// Package common Float16 provides methods to work with Hermez custom half float
|
|
// precision, 16 bits, codification internally called Float16 has been adopted
|
|
// to encode large integers. This is done in order to save bits when L2
|
|
// transactions are published.
|
|
//nolint:gomnd
|
|
package common
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"math/big"
|
|
|
|
"github.com/hermeznetwork/tracerr"
|
|
)
|
|
|
|
var (
|
|
// ErrRoundingLoss is used when converted big.Int to Float16 causes rounding loss
|
|
ErrRoundingLoss = errors.New("input value causes rounding loss")
|
|
)
|
|
|
|
// Float16 represents a float in a 16 bit format
|
|
type Float16 uint16
|
|
|
|
// Bytes return a byte array of length 2 with the Float16 value encoded in BigEndian
|
|
func (f16 Float16) Bytes() []byte {
|
|
var b [2]byte
|
|
binary.BigEndian.PutUint16(b[:], uint16(f16))
|
|
return b[:]
|
|
}
|
|
|
|
// Float16FromBytes returns a Float16 from a byte array of 2 bytes.
|
|
func Float16FromBytes(b []byte) *Float16 {
|
|
f16 := Float16(binary.BigEndian.Uint16(b[:2]))
|
|
return &f16
|
|
}
|
|
|
|
// BigInt converts the Float16 to a *big.Int integer
|
|
func (f16 *Float16) BigInt() *big.Int {
|
|
fl := int64(*f16)
|
|
|
|
m := big.NewInt(fl & 0x3FF)
|
|
e := big.NewInt(fl >> 11)
|
|
e5 := (fl >> 10) & 0x01
|
|
|
|
exp := big.NewInt(0).Exp(big.NewInt(10), e, nil)
|
|
res := m.Mul(m, exp)
|
|
|
|
if e5 != 0 && e.Cmp(big.NewInt(0)) != 0 {
|
|
res.Add(res, exp.Div(exp, big.NewInt(2)))
|
|
}
|
|
return res
|
|
}
|
|
|
|
// floorFix2Float converts a fix to a float, always rounding down
|
|
func floorFix2Float(_f *big.Int) Float16 {
|
|
zero := big.NewInt(0)
|
|
ten := big.NewInt(10)
|
|
e := int64(0)
|
|
|
|
m := big.NewInt(0)
|
|
m.Set(_f)
|
|
|
|
if m.Cmp(zero) == 0 {
|
|
return 0
|
|
}
|
|
|
|
s := big.NewInt(0).Rsh(m, 10)
|
|
|
|
for s.Cmp(zero) != 0 {
|
|
m.Div(m, ten)
|
|
s.Rsh(m, 10)
|
|
e++
|
|
}
|
|
|
|
return Float16(m.Int64() | e<<11)
|
|
}
|
|
|
|
// NewFloat16 encodes a *big.Int integer as a Float16, returning error in
|
|
// case of loss during the encoding.
|
|
func NewFloat16(f *big.Int) (Float16, error) {
|
|
fl1 := floorFix2Float(f)
|
|
fi1 := fl1.BigInt()
|
|
fl2 := fl1 | 0x400
|
|
fi2 := fl2.BigInt()
|
|
|
|
m3 := (fl1 & 0x3FF) + 1
|
|
e3 := fl1 >> 11
|
|
|
|
if m3&0x400 == 0 {
|
|
m3 = 0x66
|
|
e3++
|
|
}
|
|
|
|
fl3 := m3 + e3<<11
|
|
fi3 := fl3.BigInt()
|
|
|
|
res := fl1
|
|
|
|
d := big.NewInt(0).Abs(fi1.Sub(fi1, f))
|
|
d2 := big.NewInt(0).Abs(fi2.Sub(fi2, f))
|
|
|
|
if d.Cmp(d2) == 1 {
|
|
res = fl2
|
|
d = d2
|
|
}
|
|
|
|
d3 := big.NewInt(0).Abs(fi3.Sub(fi3, f))
|
|
|
|
if d.Cmp(d3) == 1 {
|
|
res = fl3
|
|
}
|
|
|
|
// Do rounding check
|
|
if res.BigInt().Cmp(f) == 0 {
|
|
return res, nil
|
|
}
|
|
return res, tracerr.Wrap(ErrRoundingLoss)
|
|
}
|
|
|
|
// NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in
|
|
// case of loss during the encoding.
|
|
func NewFloat16Floor(f *big.Int) Float16 {
|
|
fl1 := floorFix2Float(f)
|
|
fl2 := fl1 | 0x400
|
|
fi2 := fl2.BigInt()
|
|
|
|
if fi2.Cmp(f) < 1 {
|
|
return fl2
|
|
}
|
|
return fl1
|
|
}
|