arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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// newFloat40ME takes a *big.Int integer and returns the m (mantissa) & e
 85// (exponent) from the Float40 representation
 86func newFloat40ME(f *big.Int) (*big.Int, *big.Int) {
 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	return m, e
 96}
 97
 98// NewFloat40 encodes a *big.Int integer as a Float40, returning error in case
 99// of loss during the encoding.
100func NewFloat40(f *big.Int) (Float40, error) {
101	m, e := newFloat40ME(f)
102	if e.Int64() > 31 {
103		return 0, tracerr.Wrap(ErrFloat40E31)
104	}
105	if m.Cmp(thres) >= 0 {
106		return 0, tracerr.Wrap(ErrFloat40NotEnoughPrecission)
107	}
108	r := new(big.Int).Add(m,
109		new(big.Int).Mul(e, thres))
110	return Float40(r.Uint64()), nil
111}
112
113// NewFloat40Floor encodes a *big.Int integer as a Float40, rounding down in
114// case of loss during the encoding. It returns an error in case that the number
115// is too big (e>31). Warning: this method should not be used inside the
116// hermez-node, it's a helper for external usage to generate valid Float40
117// values.
118func NewFloat40Floor(f *big.Int) (Float40, error) {
119	m, e := newFloat40ME(f)
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}