Browse Source

Add Float40 methods

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
arnaucube 3 years ago
parent
commit
52d4197330
4 changed files with 203 additions and 5 deletions
  1. +1
    -0
      common/float16.go
  2. +5
    -5
      common/float16_test.go
  3. +102
    -0
      common/float40.go
  4. +95
    -0
      common/float40_test.go

+ 1
- 0
common/float16.go

@ -30,6 +30,7 @@ func (f16 Float16) Bytes() []byte {
// Float16FromBytes returns a Float16 from a byte array of 2 bytes. // Float16FromBytes returns a Float16 from a byte array of 2 bytes.
func Float16FromBytes(b []byte) *Float16 { func Float16FromBytes(b []byte) *Float16 {
// WARNING b[:2] for a b where len(b)<2 can break
f16 := Float16(binary.BigEndian.Uint16(b[:2])) f16 := Float16(binary.BigEndian.Uint16(b[:2]))
return &f16 return &f16
} }

+ 5
- 5
common/float16_test.go

@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestConversions(t *testing.T) {
func TestConversionsFloat16(t *testing.T) {
testVector := map[Float16]string{ testVector := map[Float16]string{
0x307B: "123000000", 0x307B: "123000000",
0x1DC6: "454500", 0x1DC6: "454500",
@ -32,14 +32,14 @@ func TestConversions(t *testing.T) {
bi.SetString(testVector[test], 10) bi.SetString(testVector[test], 10)
fl, err := NewFloat16(bi) fl, err := NewFloat16(bi)
assert.Equal(t, nil, err)
assert.NoError(t, err)
fx2 := fl.BigInt() fx2 := fl.BigInt()
assert.Equal(t, fx2.String(), testVector[test]) assert.Equal(t, fx2.String(), testVector[test])
} }
} }
func TestFloorFix2Float(t *testing.T) {
func TestFloorFix2FloatFloat16(t *testing.T) {
testVector := map[string]Float16{ testVector := map[string]Float16{
"87999990000000000": 0x776f, "87999990000000000": 0x776f,
"87950000000000001": 0x776f, "87950000000000001": 0x776f,
@ -57,10 +57,10 @@ func TestFloorFix2Float(t *testing.T) {
} }
} }
func TestConversionLosses(t *testing.T) {
func TestConversionLossesFloat16(t *testing.T) {
a := big.NewInt(1000) a := big.NewInt(1000)
b, err := NewFloat16(a) b, err := NewFloat16(a)
assert.Equal(t, nil, err)
assert.NoError(t, err)
c := b.BigInt() c := b.BigInt()
assert.Equal(t, c, a) assert.Equal(t, c, a)

+ 102
- 0
common/float40.go

@ -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
}

+ 95
- 0
common/float40_test.go

@ -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()
}
})
}

Loading…
Cancel
Save