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.

132 lines
2.7 KiB

  1. // Package common Float16 provides methods to work with Hermez custom half float
  2. // precision, 16 bits, codification internally called Float16 has been adopted
  3. // 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. var (
  14. // ErrRoundingLoss is used when converted big.Int to Float16 causes rounding loss
  15. ErrRoundingLoss = errors.New("input value causes rounding loss")
  16. )
  17. // Float16 represents a float in a 16 bit format
  18. type Float16 uint16
  19. // Bytes return a byte array of length 2 with the Float16 value encoded in BigEndian
  20. func (f16 Float16) Bytes() []byte {
  21. var b [2]byte
  22. binary.BigEndian.PutUint16(b[:], uint16(f16))
  23. return b[:]
  24. }
  25. // Float16FromBytes returns a Float16 from a byte array of 2 bytes.
  26. func Float16FromBytes(b []byte) *Float16 {
  27. // WARNING b[:2] for a b where len(b)<2 can break
  28. f16 := Float16(binary.BigEndian.Uint16(b[:2]))
  29. return &f16
  30. }
  31. // BigInt converts the Float16 to a *big.Int integer
  32. func (f16 *Float16) BigInt() *big.Int {
  33. fl := int64(*f16)
  34. m := big.NewInt(fl & 0x3FF)
  35. e := big.NewInt(fl >> 11)
  36. e5 := (fl >> 10) & 0x01
  37. exp := big.NewInt(0).Exp(big.NewInt(10), e, nil)
  38. res := m.Mul(m, exp)
  39. if e5 != 0 && e.Cmp(big.NewInt(0)) != 0 {
  40. res.Add(res, exp.Div(exp, big.NewInt(2)))
  41. }
  42. return res
  43. }
  44. // floorFix2Float converts a fix to a float, always rounding down
  45. func floorFix2Float(_f *big.Int) Float16 {
  46. zero := big.NewInt(0)
  47. ten := big.NewInt(10)
  48. e := int64(0)
  49. m := big.NewInt(0)
  50. m.Set(_f)
  51. if m.Cmp(zero) == 0 {
  52. return 0
  53. }
  54. s := big.NewInt(0).Rsh(m, 10)
  55. for s.Cmp(zero) != 0 {
  56. m.Div(m, ten)
  57. s.Rsh(m, 10)
  58. e++
  59. }
  60. return Float16(m.Int64() | e<<11)
  61. }
  62. // NewFloat16 encodes a *big.Int integer as a Float16, returning error in
  63. // case of loss during the encoding.
  64. func NewFloat16(f *big.Int) (Float16, error) {
  65. fl1 := floorFix2Float(f)
  66. fi1 := fl1.BigInt()
  67. fl2 := fl1 | 0x400
  68. fi2 := fl2.BigInt()
  69. m3 := (fl1 & 0x3FF) + 1
  70. e3 := fl1 >> 11
  71. if m3&0x400 == 0 {
  72. m3 = 0x66
  73. e3++
  74. }
  75. fl3 := m3 + e3<<11
  76. fi3 := fl3.BigInt()
  77. res := fl1
  78. d := big.NewInt(0).Abs(fi1.Sub(fi1, f))
  79. d2 := big.NewInt(0).Abs(fi2.Sub(fi2, f))
  80. if d.Cmp(d2) == 1 {
  81. res = fl2
  82. d = d2
  83. }
  84. d3 := big.NewInt(0).Abs(fi3.Sub(fi3, f))
  85. if d.Cmp(d3) == 1 {
  86. res = fl3
  87. }
  88. // Do rounding check
  89. if res.BigInt().Cmp(f) == 0 {
  90. return res, nil
  91. }
  92. return res, tracerr.Wrap(ErrRoundingLoss)
  93. }
  94. // NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in
  95. // case of loss during the encoding.
  96. func NewFloat16Floor(f *big.Int) Float16 {
  97. fl1 := floorFix2Float(f)
  98. fl2 := fl1 | 0x400
  99. fi2 := fl2.BigInt()
  100. if fi2.Cmp(f) < 1 {
  101. return fl2
  102. }
  103. return fl1
  104. }