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