arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/utils/utils.go
utils/utils.goRaw
  1// Package utils provides methods to work with Hermez custom half float
  2// precision, 16 bits, codification internally called Float16 has been adopted
  3// to encode large integers. This is done in order to save bits when L2
  4// transactions are published.
  5//nolint:gomnd
  6package utils
  7
  8import (
  9	"encoding/binary"
 10	"errors"
 11	"math/big"
 12)
 13
 14var (
 15	// ErrRoundingLoss is used when converted big.Int to Float16 causes rounding loss
 16	ErrRoundingLoss = errors.New("input value causes rounding loss")
 17)
 18
 19// Float16 represents a float in a 16 bit format
 20type Float16 uint16
 21
 22// Bytes return a byte array of length 2 with the Float16 value encoded in LittleEndian
 23func (f16 Float16) Bytes() []byte {
 24	var b [2]byte
 25	binary.LittleEndian.PutUint16(b[:], uint16(f16))
 26	return b[:]
 27}
 28
 29// BigInt converts the Float16 to a *big.Int integer
 30func (f16 *Float16) BigInt() *big.Int {
 31	fl := int64(*f16)
 32
 33	m := big.NewInt(fl & 0x3FF)
 34	e := big.NewInt(fl >> 11)
 35	e5 := (fl >> 10) & 0x01
 36
 37	exp := big.NewInt(0).Exp(big.NewInt(10), e, nil)
 38	res := m.Mul(m, exp)
 39
 40	if e5 != 0 && e.Cmp(big.NewInt(0)) != 0 {
 41		res.Add(res, exp.Div(exp, big.NewInt(2)))
 42	}
 43	return res
 44}
 45
 46// floorFix2Float converts a fix to a float, always rounding down
 47func floorFix2Float(_f *big.Int) Float16 {
 48	zero := big.NewInt(0)
 49	ten := big.NewInt(10)
 50	e := int64(0)
 51
 52	m := big.NewInt(0)
 53	m.Set(_f)
 54
 55	if m.Cmp(zero) == 0 {
 56		return 0
 57	}
 58
 59	s := big.NewInt(0).Rsh(m, 10)
 60
 61	for s.Cmp(zero) != 0 {
 62		m.Div(m, ten)
 63		s.Rsh(m, 10)
 64		e++
 65	}
 66
 67	return Float16(m.Int64() | e<<11)
 68}
 69
 70// NewFloat16 encodes a *big.Int integer as a Float16, returning error in case
 71// of loss during the encoding.
 72func NewFloat16(f *big.Int) (Float16, error) {
 73	fl1 := floorFix2Float(f)
 74	fi1 := fl1.BigInt()
 75	fl2 := fl1 | 0x400
 76	fi2 := fl2.BigInt()
 77
 78	m3 := (fl1 & 0x3FF) + 1
 79	e3 := fl1 >> 11
 80
 81	if m3&0x400 == 0 {
 82		m3 = 0x66
 83		e3++
 84	}
 85
 86	fl3 := m3 + e3<<11
 87	fi3 := fl3.BigInt()
 88
 89	res := fl1
 90
 91	d := big.NewInt(0).Abs(fi1.Sub(fi1, f))
 92	d2 := big.NewInt(0).Abs(fi2.Sub(fi2, f))
 93
 94	if d.Cmp(d2) == 1 {
 95		res = fl2
 96		d = d2
 97	}
 98
 99	d3 := big.NewInt(0).Abs(fi3.Sub(fi3, f))
100
101	if d.Cmp(d3) == 1 {
102		res = fl3
103	}
104
105	// Do rounding check
106	if res.BigInt().Cmp(f) == 0 {
107		return res, nil
108	}
109	return res, ErrRoundingLoss
110}
111
112// NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in
113// case of loss during the encoding.
114func NewFloat16Floor(f *big.Int) Float16 {
115	fl1 := floorFix2Float(f)
116	fl2 := fl1 | 0x400
117	fi2 := fl2.BigInt()
118
119	if fi2.Cmp(f) < 1 {
120		return fl2
121	}
122	return fl1
123}