Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"errors"
"fmt"
"io"
"math"
"math/big"
"math/bits"
"strings"
Expand Down Expand Up @@ -76,6 +77,42 @@ func MustFromBig(b *big.Int) *Int {
return z
}

// Float64 returns the float64 value nearest to x.
//
// Note: The `big.Float` version of `Float64` also returns an 'Accuracy', indicating
// whether the value was too small or too large to be represented by a
// `float64`. However, the `uint256` type is unable to represent values
// out of scope (|x| < math.SmallestNonzeroFloat64 or |x| > math.MaxFloat64),
// therefore this method does not return any accuracy.
func (z *Int) Float64() float64 {
if z.IsUint64() {
return float64(z.Uint64())
}
// See [1] for a detailed walkthrough of IEEE 754 conversion
//
// 1: https://www.wikihow.com/Convert-a-Number-from-Decimal-to-IEEE-754-Floating-Point-Representation

bitlen := uint64(z.BitLen())

// Normalize the number, by shifting it so that the MSB is shifted out.
y := new(Int).Lsh(z, uint(1+256-bitlen))
// The number with the leading 1 shifted out is the fraction.
fraction := y[3]

// The exp is calculated from the number of shifts, adjusted with the bias.
// double-precision uses 1023 as bias
biased_exp := 1023 + bitlen - 1

// The IEEE 754 double-precision layout is as follows:
// 1 sign bit (we don't bother with this, since it's always zero for uints)
// 11 exponent bits
// 52 fraction bits
// --------
// 64 bits

return math.Float64frombits(biased_exp<<52 | fraction>>12)
}

// SetFromHex sets z from the given string, interpreted as a hexadecimal number.
// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.
// Notable differences:
Expand Down
49 changes: 49 additions & 0 deletions conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1476,3 +1476,52 @@ func BenchmarkDecimal(b *testing.B) {
}
})
}

func testFloat64(t *testing.T, z *Int) {
bigF, _ := new(big.Float).SetInt(z.ToBig()).Float64()
_ = z.Float64() // Op must not modify the z
if have, want := z.Float64(), bigF; have != want {
t.Errorf("%s: have %f want %f", z.Hex(), have, want)
}
}

func TestFloat64(t *testing.T) {
for i := uint(0); i < 255; i++ {
z := NewInt(1)
testFloat64(t, z.Lsh(z, i))
}
}

func FuzzFloat64(f *testing.F) {
f.Fuzz(func(t *testing.T, aa, bb, cc, dd uint64) {
testFloat64(t, &Int{aa, bb, cc, dd})
})
}

func BenchmarkFloat64(b *testing.B) {
var u256Ints []*Int
var bigints []*big.Int

for i := uint(0); i < 255; i++ {
a := NewInt(1)
a.Lsh(a, i)
u256Ints = append(u256Ints, a)
bigints = append(bigints, a.ToBig())
}
b.Run("Float64/uint256", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, z := range u256Ints {
_ = z.Float64()
}
}
})
b.Run("Float64/big", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, z := range bigints {
_, _ = new(big.Float).SetInt(z).Float64()
}
}
})
}