This commit adds Float40 related methods, and keeps the Float16 version which will be deleted in a near future once the Float40 migration is ready.feature/float40-zkinputs
@ -0,0 +1,102 @@ |
|||
// Package common Float40 provides methods to work with Hermez custom half
|
|||
// float precision, 40 bits, codification internally called Float40 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 ( |
|||
"bytes" |
|||
"encoding/binary" |
|||
"errors" |
|||
"math/big" |
|||
|
|||
"github.com/hermeznetwork/tracerr" |
|||
) |
|||
|
|||
const ( |
|||
// maxFloat40Value is the maximum value that the Float40 can have
|
|||
// (40 bits: maxFloat40Value=2**40-1)
|
|||
maxFloat40Value = 0xffffffffff |
|||
) |
|||
|
|||
var ( |
|||
// ErrFloat40Overflow is used when a given nonce overflows the maximum
|
|||
// capacity of the Float40 (2**40-1)
|
|||
ErrFloat40Overflow = errors.New("Float40 overflow, max value: 2**40 -1") |
|||
// ErrFloat40E31 is used when the e > 31 when trying to convert a
|
|||
// *big.Int to Float40
|
|||
ErrFloat40E31 = errors.New("Float40 error, e > 31") |
|||
// ErrFloat40NotEnoughPrecission is used when the given *big.Int can
|
|||
// not be represented as Float40 due not enough precission
|
|||
ErrFloat40NotEnoughPrecission = errors.New("Float40 error, not enough precission") |
|||
) |
|||
|
|||
// Float40 represents a float in a 64 bit format
|
|||
type Float40 uint64 |
|||
|
|||
// Bytes return a byte array of length 5 with the Float40 value encoded in
|
|||
// BigEndian
|
|||
func (f40 Float40) Bytes() ([]byte, error) { |
|||
if f40 > maxFloat40Value { |
|||
return []byte{}, tracerr.Wrap(ErrFloat40Overflow) |
|||
} |
|||
|
|||
var f40Bytes [8]byte |
|||
binary.BigEndian.PutUint64(f40Bytes[:], uint64(f40)) |
|||
var b [5]byte |
|||
copy(b[:], f40Bytes[3:]) |
|||
return b[:], nil |
|||
} |
|||
|
|||
// Float40FromBytes returns a Float40 from a byte array of 5 bytes in Bigendian
|
|||
// representation.
|
|||
func Float40FromBytes(b []byte) Float40 { |
|||
var f40Bytes [8]byte |
|||
copy(f40Bytes[3:], b[:]) |
|||
f40 := binary.BigEndian.Uint64(f40Bytes[:]) |
|||
return Float40(f40) |
|||
} |
|||
|
|||
// BigInt converts the Float40 to a *big.Int v, where v = m * 10^e, being:
|
|||
// [ e | m ]
|
|||
// [ 5 bits | 35 bits ]
|
|||
func (f40 Float40) BigInt() (*big.Int, error) { |
|||
// take the 5 used bytes (FF * 5)
|
|||
var f40Uint64 uint64 = uint64(f40) & 0x00_00_00_FF_FF_FF_FF_FF |
|||
f40Bytes, err := f40.Bytes() |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
e := f40Bytes[0] & 0xF8 >> 3 // take first 5 bits
|
|||
m := f40Uint64 & 0x07_FF_FF_FF_FF // take the others 35 bits
|
|||
|
|||
exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(e)), nil) |
|||
r := new(big.Int).Mul(big.NewInt(int64(m)), exp) |
|||
return r, nil |
|||
} |
|||
|
|||
// NewFloat40 encodes a *big.Int integer as a Float40, returning error in case
|
|||
// of loss during the encoding.
|
|||
func NewFloat40(f *big.Int) (Float40, error) { |
|||
m := f |
|||
e := big.NewInt(0) |
|||
zero := big.NewInt(0) |
|||
ten := big.NewInt(10) |
|||
thres := big.NewInt(0x08_00_00_00_00) |
|||
for bytes.Equal(zero.Bytes(), new(big.Int).Mod(m, ten).Bytes()) && |
|||
!bytes.Equal(zero.Bytes(), new(big.Int).Div(m, thres).Bytes()) { |
|||
m = new(big.Int).Div(m, ten) |
|||
e = new(big.Int).Add(e, big.NewInt(1)) |
|||
} |
|||
if e.Int64() > 31 { |
|||
return 0, ErrFloat40E31 |
|||
} |
|||
if !bytes.Equal(zero.Bytes(), new(big.Int).Div(m, thres).Bytes()) { |
|||
return 0, ErrFloat40NotEnoughPrecission |
|||
} |
|||
r := new(big.Int).Add(m, |
|||
new(big.Int).Mul(e, thres)) |
|||
return Float40(r.Uint64()), nil |
|||
} |
@ -0,0 +1,95 @@ |
|||
package common |
|||
|
|||
import ( |
|||
"math/big" |
|||
"testing" |
|||
|
|||
"github.com/stretchr/testify/assert" |
|||
"github.com/stretchr/testify/require" |
|||
) |
|||
|
|||
func TestConversionsFloat40(t *testing.T) { |
|||
testVector := map[Float40]string{ |
|||
6*0x800000000 + 123: "123000000", |
|||
2*0x800000000 + 4545: "454500", |
|||
30*0x800000000 + 10235: "10235000000000000000000000000000000", |
|||
0x000000000: "0", |
|||
0x800000000: "0", |
|||
0x0001: "1", |
|||
0x0401: "1025", |
|||
0x800000000 + 1: "10", |
|||
0xFFFFFFFFFF: "343597383670000000000000000000000000000000", |
|||
} |
|||
|
|||
for test := range testVector { |
|||
fix, err := test.BigInt() |
|||
require.NoError(t, err) |
|||
assert.Equal(t, fix.String(), testVector[test]) |
|||
|
|||
bi, ok := new(big.Int).SetString(testVector[test], 10) |
|||
require.True(t, ok) |
|||
|
|||
fl, err := NewFloat40(bi) |
|||
assert.NoError(t, err) |
|||
|
|||
fx2, err := fl.BigInt() |
|||
require.NoError(t, err) |
|||
assert.Equal(t, fx2.String(), testVector[test]) |
|||
} |
|||
} |
|||
|
|||
func TestExpectError(t *testing.T) { |
|||
testVector := map[string]error{ |
|||
"9922334455000000000000000000000000000000": nil, |
|||
"9922334455000000000000000000000000000001": ErrFloat40NotEnoughPrecission, |
|||
"9922334454999999999999999999999999999999": ErrFloat40NotEnoughPrecission, |
|||
"42949672950000000000000000000000000000000": nil, |
|||
"99223344556573838487575": ErrFloat40NotEnoughPrecission, |
|||
"992233445500000000000000000000000000000000": ErrFloat40E31, |
|||
"343597383670000000000000000000000000000000": nil, |
|||
"343597383680000000000000000000000000000000": ErrFloat40NotEnoughPrecission, |
|||
"343597383690000000000000000000000000000000": ErrFloat40NotEnoughPrecission, |
|||
"343597383700000000000000000000000000000000": ErrFloat40E31, |
|||
} |
|||
for test := range testVector { |
|||
bi, ok := new(big.Int).SetString(test, 10) |
|||
require.True(t, ok) |
|||
_, err := NewFloat40(bi) |
|||
assert.Equal(t, testVector[test], err) |
|||
} |
|||
} |
|||
|
|||
func BenchmarkFloat40(b *testing.B) { |
|||
newBigInt := func(s string) *big.Int { |
|||
bigInt, ok := new(big.Int).SetString(s, 10) |
|||
if !ok { |
|||
panic("Can not convert string to *big.Int") |
|||
} |
|||
return bigInt |
|||
} |
|||
type pair struct { |
|||
Float40 Float40 |
|||
BigInt *big.Int |
|||
} |
|||
testVector := []pair{ |
|||
{6*0x800000000 + 123, newBigInt("123000000")}, |
|||
{2*0x800000000 + 4545, newBigInt("454500")}, |
|||
{30*0x800000000 + 10235, newBigInt("10235000000000000000000000000000000")}, |
|||
{0x000000000, newBigInt("0")}, |
|||
{0x800000000, newBigInt("0")}, |
|||
{0x0001, newBigInt("1")}, |
|||
{0x0401, newBigInt("1025")}, |
|||
{0x800000000 + 1, newBigInt("10")}, |
|||
{0xFFFFFFFFFF, newBigInt("343597383670000000000000000000000000000000")}, |
|||
} |
|||
b.Run("NewFloat40()", func(b *testing.B) { |
|||
for i := 0; i < b.N; i++ { |
|||
_, _ = NewFloat40(testVector[i%len(testVector)].BigInt) |
|||
} |
|||
}) |
|||
b.Run("Float40.BigInt()", func(b *testing.B) { |
|||
for i := 0; i < b.N; i++ { |
|||
_, _ = testVector[i%len(testVector)].Float40.BigInt() |
|||
} |
|||
}) |
|||
} |