hermez-node/utils/utils.go
utils/utils.goRaw
1// Package utils 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
6package utils
7
8import (
9 "encoding/binary"
10 "errors"
11 "math/big"
12)
13
14var (
15 // ErrRoundingLoss is used when converted big.Int to Float16 causes rounding loss
16 ErrRoundingLoss = errors.New("input value causes rounding loss")
17)
18
19// Float16 represents a float in a 16 bit format
20type Float16 uint16
21
22// Bytes return a byte array of length 2 with the Float16 value encoded in LittleEndian
23func (f16 Float16) Bytes() []byte {
24 var b [2]byte
25 binary.LittleEndian.PutUint16(b[:], uint16(f16))
26 return b[:]
27}
28
29// BigInt converts the Float16 to a *big.Int integer
30func (f16 *Float16) BigInt() *big.Int {
31 fl := int64(*f16)
32
33 m := big.NewInt(fl & 0x3FF)
34 e := big.NewInt(fl >> 11)
35 e5 := (fl >> 10) & 0x01
36
37 exp := big.NewInt(0).Exp(big.NewInt(10), e, nil)
38 res := m.Mul(m, exp)
39
40 if e5 != 0 && e.Cmp(big.NewInt(0)) != 0 {
41 res.Add(res, exp.Div(exp, big.NewInt(2)))
42 }
43 return res
44}
45
46// floorFix2Float converts a fix to a float, always rounding down
47func floorFix2Float(_f *big.Int) Float16 {
48 zero := big.NewInt(0)
49 ten := big.NewInt(10)
50 e := int64(0)
51
52 m := big.NewInt(0)
53 m.Set(_f)
54
55 if m.Cmp(zero) == 0 {
56 return 0
57 }
58
59 s := big.NewInt(0).Rsh(m, 10)
60
61 for s.Cmp(zero) != 0 {
62 m.Div(m, ten)
63 s.Rsh(m, 10)
64 e++
65 }
66
67 return Float16(m.Int64() | e<<11)
68}
69
70// NewFloat16 encodes a *big.Int integer as a Float16, returning error in case
71// of loss during the encoding.
72func NewFloat16(f *big.Int) (Float16, error) {
73 fl1 := floorFix2Float(f)
74 fi1 := fl1.BigInt()
75 fl2 := fl1 | 0x400
76 fi2 := fl2.BigInt()
77
78 m3 := (fl1 & 0x3FF) + 1
79 e3 := fl1 >> 11
80
81 if m3&0x400 == 0 {
82 m3 = 0x66
83 e3++
84 }
85
86 fl3 := m3 + e3<<11
87 fi3 := fl3.BigInt()
88
89 res := fl1
90
91 d := big.NewInt(0).Abs(fi1.Sub(fi1, f))
92 d2 := big.NewInt(0).Abs(fi2.Sub(fi2, f))
93
94 if d.Cmp(d2) == 1 {
95 res = fl2
96 d = d2
97 }
98
99 d3 := big.NewInt(0).Abs(fi3.Sub(fi3, f))
100
101 if d.Cmp(d3) == 1 {
102 res = fl3
103 }
104
105 // Do rounding check
106 if res.BigInt().Cmp(f) == 0 {
107 return res, nil
108 }
109 return res, ErrRoundingLoss
110}
111
112// NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in
113// case of loss during the encoding.
114func NewFloat16Floor(f *big.Int) Float16 {
115 fl1 := floorFix2Float(f)
116 fl2 := fl1 | 0x400
117 fi2 := fl2.BigInt()
118
119 if fi2.Cmp(f) < 1 {
120 return fl2
121 }
122 return fl1
123}