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