You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
3.8 KiB

  1. // Package common float40.go provides methods to work with Hermez custom half
  2. // float precision, 40 bits, codification internally called Float40 has been
  3. // adopted to encode large integers. This is done in order to save bits when L2
  4. // transactions are published.
  5. //nolint:gomnd
  6. package common
  7. import (
  8. "encoding/binary"
  9. "errors"
  10. "math/big"
  11. "github.com/hermeznetwork/tracerr"
  12. )
  13. const (
  14. // maxFloat40Value is the maximum value that the Float40 can have
  15. // (40 bits: maxFloat40Value=2**40-1)
  16. maxFloat40Value = 0xffffffffff
  17. // Float40BytesLength defines the length of the Float40 values
  18. // represented as byte arrays
  19. Float40BytesLength = 5
  20. )
  21. var (
  22. // ErrFloat40Overflow is used when a given Float40 overflows the
  23. // maximum capacity of the Float40 (2**40-1)
  24. ErrFloat40Overflow = errors.New("Float40 overflow, max value: 2**40 -1")
  25. // ErrFloat40E31 is used when the e > 31 when trying to convert a
  26. // *big.Int to Float40
  27. ErrFloat40E31 = errors.New("Float40 error, e > 31")
  28. // ErrFloat40NotEnoughPrecission is used when the given *big.Int can
  29. // not be represented as Float40 due not enough precission
  30. ErrFloat40NotEnoughPrecission = errors.New("Float40 error, not enough precission")
  31. thres = big.NewInt(0x08_00_00_00_00)
  32. )
  33. // Float40 represents a float in a 64 bit format
  34. type Float40 uint64
  35. // Bytes return a byte array of length 5 with the Float40 value encoded in
  36. // BigEndian
  37. func (f40 Float40) Bytes() ([]byte, error) {
  38. if f40 > maxFloat40Value {
  39. return []byte{}, tracerr.Wrap(ErrFloat40Overflow)
  40. }
  41. var f40Bytes [8]byte
  42. binary.BigEndian.PutUint64(f40Bytes[:], uint64(f40))
  43. var b [5]byte
  44. copy(b[:], f40Bytes[3:])
  45. return b[:], nil
  46. }
  47. // Float40FromBytes returns a Float40 from a byte array of 5 bytes in Bigendian
  48. // representation.
  49. func Float40FromBytes(b []byte) Float40 {
  50. var f40Bytes [8]byte
  51. copy(f40Bytes[3:], b[:])
  52. f40 := binary.BigEndian.Uint64(f40Bytes[:])
  53. return Float40(f40)
  54. }
  55. // BigInt converts the Float40 to a *big.Int v, where v = m * 10^e, being:
  56. // [ e | m ]
  57. // [ 5 bits | 35 bits ]
  58. func (f40 Float40) BigInt() (*big.Int, error) {
  59. // take the 5 used bytes (FF * 5)
  60. var f40Uint64 uint64 = uint64(f40) & 0x00_00_00_FF_FF_FF_FF_FF
  61. f40Bytes, err := f40.Bytes()
  62. if err != nil {
  63. return nil, tracerr.Wrap(err)
  64. }
  65. e := f40Bytes[0] & 0xF8 >> 3 // take first 5 bits
  66. m := f40Uint64 & 0x07_FF_FF_FF_FF // take the others 35 bits
  67. exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(e)), nil)
  68. r := new(big.Int).Mul(big.NewInt(int64(m)), exp)
  69. return r, nil
  70. }
  71. // newFloat40ME takes a *big.Int integer and returns the m (mantissa) & e
  72. // (exponent) from the Float40 representation
  73. func newFloat40ME(f *big.Int) (*big.Int, *big.Int) {
  74. m := f
  75. e := big.NewInt(0)
  76. zero := big.NewInt(0)
  77. ten := big.NewInt(10)
  78. for new(big.Int).Mod(m, ten).Cmp(zero) == 0 && m.Cmp(thres) >= 0 {
  79. m = new(big.Int).Div(m, ten)
  80. e = new(big.Int).Add(e, big.NewInt(1))
  81. }
  82. return m, e
  83. }
  84. // NewFloat40 encodes a *big.Int integer as a Float40, returning error in case
  85. // of loss during the encoding.
  86. func NewFloat40(f *big.Int) (Float40, error) {
  87. m, e := newFloat40ME(f)
  88. if e.Int64() > 31 {
  89. return 0, tracerr.Wrap(ErrFloat40E31)
  90. }
  91. if m.Cmp(thres) >= 0 {
  92. return 0, tracerr.Wrap(ErrFloat40NotEnoughPrecission)
  93. }
  94. r := new(big.Int).Add(m,
  95. new(big.Int).Mul(e, thres))
  96. return Float40(r.Uint64()), nil
  97. }
  98. // NewFloat40Floor encodes a *big.Int integer as a Float40, rounding down in
  99. // case of loss during the encoding. It returns an error in case that the number
  100. // is too big (e>31). Warning: this method should not be used inside the
  101. // hermez-node, it's a helper for external usage to generate valid Float40
  102. // values.
  103. func NewFloat40Floor(f *big.Int) (Float40, error) {
  104. m, e := newFloat40ME(f)
  105. if e.Int64() > 31 {
  106. return 0, tracerr.Wrap(ErrFloat40E31)
  107. }
  108. r := new(big.Int).Add(m,
  109. new(big.Int).Mul(e, thres))
  110. return Float40(r.Uint64()), nil
  111. }