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