arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/common/float16.go
common/float16.goRaw
  1// Package common Float16 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 common
  7
  8import (
  9	"encoding/binary"
 10	"errors"
 11	"math/big"
 12
 13	"github.com/hermeznetwork/tracerr"
 14)
 15
 16var (
 17	// ErrRoundingLoss is used when converted big.Int to Float16 causes rounding loss
 18	ErrRoundingLoss = errors.New("input value causes rounding loss")
 19)
 20
 21// Float16 represents a float in a 16 bit format
 22type Float16 uint16
 23
 24// Bytes return a byte array of length 2 with the Float16 value encoded in BigEndian
 25func (f16 Float16) Bytes() []byte {
 26	var b [2]byte
 27	binary.BigEndian.PutUint16(b[:], uint16(f16))
 28	return b[:]
 29}
 30
 31// Float16FromBytes returns a Float16 from a byte array of 2 bytes.
 32func Float16FromBytes(b []byte) *Float16 {
 33	// WARNING b[:2] for a b where len(b)<2 can break
 34	f16 := Float16(binary.BigEndian.Uint16(b[:2]))
 35	return &f16
 36}
 37
 38// BigInt converts the Float16 to a *big.Int integer
 39func (f16 *Float16) BigInt() *big.Int {
 40	fl := int64(*f16)
 41
 42	m := big.NewInt(fl & 0x3FF)
 43	e := big.NewInt(fl >> 11)
 44	e5 := (fl >> 10) & 0x01
 45
 46	exp := big.NewInt(0).Exp(big.NewInt(10), e, nil)
 47	res := m.Mul(m, exp)
 48
 49	if e5 != 0 && e.Cmp(big.NewInt(0)) != 0 {
 50		res.Add(res, exp.Div(exp, big.NewInt(2)))
 51	}
 52	return res
 53}
 54
 55// floorFix2Float converts a fix to a float, always rounding down
 56func floorFix2Float(_f *big.Int) Float16 {
 57	zero := big.NewInt(0)
 58	ten := big.NewInt(10)
 59	e := int64(0)
 60
 61	m := big.NewInt(0)
 62	m.Set(_f)
 63
 64	if m.Cmp(zero) == 0 {
 65		return 0
 66	}
 67
 68	s := big.NewInt(0).Rsh(m, 10)
 69
 70	for s.Cmp(zero) != 0 {
 71		m.Div(m, ten)
 72		s.Rsh(m, 10)
 73		e++
 74	}
 75
 76	return Float16(m.Int64() | e<<11)
 77}
 78
 79// NewFloat16 encodes a *big.Int integer as a Float16, returning error in
 80// case of loss during the encoding.
 81func NewFloat16(f *big.Int) (Float16, error) {
 82	fl1 := floorFix2Float(f)
 83	fi1 := fl1.BigInt()
 84	fl2 := fl1 | 0x400
 85	fi2 := fl2.BigInt()
 86
 87	m3 := (fl1 & 0x3FF) + 1
 88	e3 := fl1 >> 11
 89
 90	if m3&0x400 == 0 {
 91		m3 = 0x66
 92		e3++
 93	}
 94
 95	fl3 := m3 + e3<<11
 96	fi3 := fl3.BigInt()
 97
 98	res := fl1
 99
100	d := big.NewInt(0).Abs(fi1.Sub(fi1, f))
101	d2 := big.NewInt(0).Abs(fi2.Sub(fi2, f))
102
103	if d.Cmp(d2) == 1 {
104		res = fl2
105		d = d2
106	}
107
108	d3 := big.NewInt(0).Abs(fi3.Sub(fi3, f))
109
110	if d.Cmp(d3) == 1 {
111		res = fl3
112	}
113
114	// Do rounding check
115	if res.BigInt().Cmp(f) == 0 {
116		return res, nil
117	}
118	return res, tracerr.Wrap(ErrRoundingLoss)
119}
120
121// NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in
122// case of loss during the encoding.
123func NewFloat16Floor(f *big.Int) Float16 {
124	fl1 := floorFix2Float(f)
125	fl2 := fl1 | 0x400
126	fi2 := fl2.BigInt()
127
128	if fi2.Cmp(f) < 1 {
129		return fl2
130	}
131	return fl1
132}