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
 37// Float40 represents a float in a 64 bit format
 38type Float40 uint64
 39
 40// Bytes return a byte array of length 5 with the Float40 value encoded in
 41// BigEndian
 42func (f40 Float40) Bytes() ([]byte, error) {
 43	if f40 > maxFloat40Value {
 44		return []byte{}, tracerr.Wrap(ErrFloat40Overflow)
 45	}
 46
 47	var f40Bytes [8]byte
 48	binary.BigEndian.PutUint64(f40Bytes[:], uint64(f40))
 49	var b [5]byte
 50	copy(b[:], f40Bytes[3:])
 51	return b[:], nil
 52}
 53
 54// Float40FromBytes returns a Float40 from a byte array of 5 bytes in Bigendian
 55// representation.
 56func Float40FromBytes(b []byte) Float40 {
 57	var f40Bytes [8]byte
 58	copy(f40Bytes[3:], b[:])
 59	f40 := binary.BigEndian.Uint64(f40Bytes[:])
 60	return Float40(f40)
 61}
 62
 63// BigInt converts the Float40 to a *big.Int v, where v = m * 10^e, being:
 64// [    e   |    m    ]
 65// [ 5 bits | 35 bits ]
 66func (f40 Float40) BigInt() (*big.Int, error) {
 67	// take the 5 used bytes (FF * 5)
 68	var f40Uint64 uint64 = uint64(f40) & 0x00_00_00_FF_FF_FF_FF_FF
 69	f40Bytes, err := f40.Bytes()
 70	if err != nil {
 71		return nil, err
 72	}
 73
 74	e := f40Bytes[0] & 0xF8 >> 3      // take first 5 bits
 75	m := f40Uint64 & 0x07_FF_FF_FF_FF // take the others 35 bits
 76
 77	exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(e)), nil)
 78	r := new(big.Int).Mul(big.NewInt(int64(m)), exp)
 79	return r, nil
 80}
 81
 82// NewFloat40 encodes a *big.Int integer as a Float40, returning error in case
 83// of loss during the encoding.
 84func NewFloat40(f *big.Int) (Float40, error) {
 85	m := f
 86	e := big.NewInt(0)
 87	zero := big.NewInt(0)
 88	ten := big.NewInt(10)
 89	thres := big.NewInt(0x08_00_00_00_00)
 90	for new(big.Int).Mod(m, ten).Cmp(zero) == 0 && m.Cmp(thres) >= 0 {
 91		m = new(big.Int).Div(m, ten)
 92		e = new(big.Int).Add(e, big.NewInt(1))
 93	}
 94	if e.Int64() > 31 {
 95		return 0, ErrFloat40E31
 96	}
 97	if m.Cmp(thres) >= 0 {
 98		return 0, ErrFloat40NotEnoughPrecission
 99	}
100	r := new(big.Int).Add(m,
101		new(big.Int).Mul(e, thres))
102	return Float40(r.Uint64()), nil
103}