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