diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go index cafb6cad67f..89fc3e0973d 100644 --- a/gno.land/pkg/sdk/vm/convert.go +++ b/gno.land/pkg/sdk/vm/convert.go @@ -143,11 +143,11 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { return case gno.Float32Type: value := convertFloat(arg, 32) - tv.SetFloat32(float32(value)) + tv.SetFloat32(gno.ConvertToSoftFloat32(value)) return case gno.Float64Type: value := convertFloat(arg, 64) - tv.SetFloat64(value) + tv.SetFloat64(gno.ConvertToSoftFloat64(value)) return default: panic(fmt.Sprintf("unexpected primitive type %s", bt.String())) diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 5a39c76b5e1..e77c7f99474 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -329,9 +329,9 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.Uint64: tv.SetUint64(rv.Uint()) case reflect.Float32: - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(ConvertToSoftFloat32(rv.Float())) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(ConvertToSoftFloat64(rv.Float())) case reflect.Array: tv.V = alloc.NewNative(rv) case reflect.Slice: @@ -428,11 +428,11 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case Float32Kind: if lvl != 0 { - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(ConvertToSoftFloat32(rv.Float())) } case Float64Kind: if lvl != 0 { - tv.SetFloat64(rv.Float()) + tv.SetFloat64(ConvertToSoftFloat64(rv.Float())) } case BigintKind: panic("not yet implemented") @@ -644,9 +644,9 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.Uint64: tv.SetUint64(rv.Uint()) case reflect.Float32: - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(ConvertToSoftFloat32(rv.Float())) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(ConvertToSoftFloat64(rv.Float())) case reflect.Array: rvl := rv.Len() if rv.Type().Elem().Kind() == reflect.Uint8 { @@ -1049,9 +1049,9 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { case Uint64Type: rv.SetUint(tv.GetUint64()) case Float32Type: - rv.SetFloat(float64(tv.GetFloat32())) + rv.SetFloat(tv.GetFloat32().Float64()) case Float64Type: - rv.SetFloat(tv.GetFloat64()) + rv.SetFloat(tv.GetFloat64().Float64()) default: panic(fmt.Sprintf( "unexpected type %s", diff --git a/gnovm/pkg/gnolang/internal/softfloat/copy.sh b/gnovm/pkg/gnolang/internal/softfloat/copy.sh new file mode 100644 index 00000000000..6d2a8f80462 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/copy.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# softfloat64.go: +# - add header +# - change package name +cat > runtime_softfloat64.go << EOF +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from \$GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +EOF +cat "$GOROOT/src/runtime/softfloat64.go" >> ./runtime_softfloat64.go +sed -i 's/^package runtime$/package softfloat/' runtime_softfloat64.go + +# softfloat64_test.go: +# - add header +# - change package name +# - change import to right package +# - change GOARCH to runtime.GOARCH, and import the "runtime" package +cat > runtime_softfloat64_test.go << EOF +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from \$GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +EOF +cat "$GOROOT/src/runtime/softfloat64_test.go" >> ./runtime_softfloat64_test.go +sed -i 's/^package runtime_test$/package softfloat_test/ +s#^\t\. "runtime"$#\t. "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"# +s/GOARCH/runtime.GOARCH/g +16a\ + "runtime"' runtime_softfloat64_test.go \ No newline at end of file diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go new file mode 100644 index 00000000000..cf2ad5afd8a --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go @@ -0,0 +1,631 @@ +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Software IEEE754 64-bit floating point. +// Only referred to (and thus linked in) by softfloat targets +// and by tests in this directory. + +package softfloat + +const ( + mantbits64 uint = 52 + expbits64 uint = 11 + bias64 = -1<<(expbits64-1) + 1 + + nan64 uint64 = (1<>mantbits64) & (1<>mantbits32) & (1<= 4<>= 1 + exp++ + } + if mant >= 2<= 4<>= 1 + exp++ + } + } + mant >>= 1 + exp++ + } + if exp >= 1<>= 1 + exp++ + } + if mant&1 != 0 && (trunc != 0 || mant&2 != 0) { + mant++ + } + mant >>= 1 + exp++ + if mant < 1<= 4<>= 1 + exp++ + } + if mant >= 2<= 4<>= 1 + exp++ + } + } + mant >>= 1 + exp++ + } + if exp >= 1<>= 1 + exp++ + } + if mant&1 != 0 && (trunc != 0 || mant&2 != 0) { + mant++ + } + mant >>= 1 + exp++ + if mant < 1<>= shift + if fs == gs { + fm += gm + } else { + fm -= gm + if trunc != 0 { + fm-- + } + } + if fm == 0 { + fs = 0 + } + return fpack64(fs, fm, fe-2, trunc) +} + +func fsub64(f, g uint64) uint64 { + return fadd64(f, fneg64(g)) +} + +func fneg64(f uint64) uint64 { + return f ^ (1 << (mantbits64 + expbits64)) +} + +func fmul64(f, g uint64) uint64 { + fs, fm, fe, fi, fn := funpack64(f) + gs, gm, ge, gi, gn := funpack64(g) + + // Special cases. + switch { + case fn || gn: // NaN * g or f * NaN = NaN + return nan64 + + case fi && gi: // Inf * Inf = Inf (with sign adjusted) + return f ^ gs + + case fi && gm == 0, fm == 0 && gi: // 0 * Inf = Inf * 0 = NaN + return nan64 + + case fm == 0: // 0 * x = 0 (with sign adjusted) + return f ^ gs + + case gm == 0: // x * 0 = 0 (with sign adjusted) + return g ^ fs + } + + // 53-bit * 53-bit = 107- or 108-bit + lo, hi := mullu(fm, gm) + shift := mantbits64 - 1 + trunc := lo & (1<>shift + return fpack64(fs^gs, mant, fe+ge-1, trunc) +} + +func fdiv64(f, g uint64) uint64 { + fs, fm, fe, fi, fn := funpack64(f) + gs, gm, ge, gi, gn := funpack64(g) + + // Special cases. + switch { + case fn || gn: // NaN / g = f / NaN = NaN + return nan64 + + case fi && gi: // ±Inf / ±Inf = NaN + return nan64 + + case !fi && !gi && fm == 0 && gm == 0: // 0 / 0 = NaN + return nan64 + + case fi, !gi && gm == 0: // Inf / g = f / 0 = Inf + return fs ^ gs ^ inf64 + + case gi, fm == 0: // f / Inf = 0 / g = Inf + return fs ^ gs ^ 0 + } + _, _, _, _ = fi, fn, gi, gn + + // 53-bit<<54 / 53-bit = 53- or 54-bit. + shift := mantbits64 + 2 + q, r := divlu(fm>>(64-shift), fm<> 32) + if fi { + return fs32 ^ inf32 + } + const d = mantbits64 - mantbits32 - 1 + return fpack32(fs32, uint32(fm>>d), fe-1, uint32(fm&(1< gs: // f < 0, g > 0 + return -1, false + + case fs < gs: // f > 0, g < 0 + return +1, false + + // Same sign, not NaN. + // Can compare encodings directly now. + // Reverse for sign. + case fs == 0 && f < g, fs != 0 && f > g: + return -1, false + + case fs == 0 && f > g, fs != 0 && f < g: + return +1, false + } + + // f == g + return 0, false +} + +func f64toint(f uint64) (val int64, ok bool) { + fs, fm, fe, fi, fn := funpack64(f) + + switch { + case fi, fn: // NaN + return 0, false + + case fe < -1: // f < 0.5 + return 0, false + + case fe > 63: // f >= 2^63 + if fs != 0 && fm == 0 { // f == -2^63 + return -1 << 63, true + } + if fs != 0 { + return 0, false + } + return 0, false + } + + for fe > int(mantbits64) { + fe-- + fm <<= 1 + } + for fe < int(mantbits64) { + fe++ + fm >>= 1 + } + val = int64(fm) + if fs != 0 { + val = -val + } + return val, true +} + +func fintto64(val int64) (f uint64) { + fs := uint64(val) & (1 << 63) + mant := uint64(val) + if fs != 0 { + mant = -mant + } + return fpack64(fs, mant, int(mantbits64), 0) +} +func fintto32(val int64) (f uint32) { + fs := uint64(val) & (1 << 63) + mant := uint64(val) + if fs != 0 { + mant = -mant + } + // Reduce mantissa size until it fits into a uint32. + // Keep track of the bits we throw away, and if any are + // nonzero or them into the lowest bit. + exp := int(mantbits32) + var trunc uint32 + for mant >= 1<<32 { + trunc |= uint32(mant) & 1 + mant >>= 1 + exp++ + } + + return fpack32(uint32(fs>>32), uint32(mant), exp, trunc) +} + +// 64x64 -> 128 multiply. +// adapted from hacker's delight. +func mullu(u, v uint64) (lo, hi uint64) { + const ( + s = 32 + mask = 1<> s + v0 := v & mask + v1 := v >> s + w0 := u0 * v0 + t := u1*v0 + w0>>s + w1 := t & mask + w2 := t >> s + w1 += u0 * v1 + return u * v, u1*v1 + w2 + w1>>s +} + +// 128/64 -> 64 quotient, 64 remainder. +// adapted from hacker's delight +func divlu(u1, u0, v uint64) (q, r uint64) { + const b = 1 << 32 + + if u1 >= v { + return 1<<64 - 1, 1<<64 - 1 + } + + // s = nlz(v); v <<= s + s := uint(0) + for v&(1<<63) == 0 { + s++ + v <<= 1 + } + + vn1 := v >> 32 + vn0 := v & (1<<32 - 1) + un32 := u1<>(64-s) + un10 := u0 << s + un1 := un10 >> 32 + un0 := un10 & (1<<32 - 1) + q1 := un32 / vn1 + rhat := un32 - q1*vn1 + +again1: + if q1 >= b || q1*vn0 > b*rhat+un1 { + q1-- + rhat += vn1 + if rhat < b { + goto again1 + } + } + + un21 := un32*b + un1 - q1*v + q0 := un21 / vn1 + rhat = un21 - q0*vn1 + +again2: + if q0 >= b || q0*vn0 > b*rhat+un0 { + q0-- + rhat += vn1 + if rhat < b { + goto again2 + } + } + + return q1*b + q0, (un21*b + un0 - q0*v) >> s +} + +func fadd32(x, y uint32) uint32 { + return f64to32(fadd64(f32to64(x), f32to64(y))) +} + +func fmul32(x, y uint32) uint32 { + return f64to32(fmul64(f32to64(x), f32to64(y))) +} + +func fdiv32(x, y uint32) uint32 { + // TODO: are there double-rounding problems here? See issue 48807. + return f64to32(fdiv64(f32to64(x), f32to64(y))) +} + +func feq32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp == 0 && !nan +} + +func fgt32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp >= 1 && !nan +} + +func fge32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp >= 0 && !nan +} + +func feq64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp == 0 && !nan +} + +func fgt64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp >= 1 && !nan +} + +func fge64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp >= 0 && !nan +} + +func fint32to32(x int32) uint32 { + return fintto32(int64(x)) +} + +func fint32to64(x int32) uint64 { + return fintto64(int64(x)) +} + +func fint64to32(x int64) uint32 { + return fintto32(x) +} + +func fint64to64(x int64) uint64 { + return fintto64(x) +} + +func f32toint32(x uint32) int32 { + val, _ := f64toint(f32to64(x)) + return int32(val) +} + +func f32toint64(x uint32) int64 { + val, _ := f64toint(f32to64(x)) + return val +} + +func f64toint32(x uint64) int32 { + val, _ := f64toint(x) + return int32(val) +} + +func f64toint64(x uint64) int64 { + val, _ := f64toint(x) + return val +} + +func f64touint64(x uint64) uint64 { + var m uint64 = 0x43e0000000000000 // float64 1<<63 + if fgt64(m, x) { + return uint64(f64toint64(x)) + } + y := fadd64(x, -m) + z := uint64(f64toint64(y)) + return z | (1 << 63) +} + +func f32touint64(x uint32) uint64 { + var m uint32 = 0x5f000000 // float32 1<<63 + if fgt32(m, x) { + return uint64(f32toint64(x)) + } + y := fadd32(x, -m) + z := uint64(f32toint64(y)) + return z | (1 << 63) +} + +func fuint64to64(x uint64) uint64 { + if int64(x) >= 0 { + return fint64to64(int64(x)) + } + // See ../cmd/compile/internal/ssagen/ssa.go:uint64Tofloat + y := x & 1 + z := x >> 1 + z = z | y + r := fint64to64(int64(z)) + return fadd64(r, r) +} + +func fuint64to32(x uint64) uint32 { + if int64(x) >= 0 { + return fint64to32(int64(x)) + } + // See ../cmd/compile/internal/ssagen/ssa.go:uint64Tofloat + y := x & 1 + z := x >> 1 + z = z | y + r := fint64to32(int64(z)) + return fadd32(r, r) +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go new file mode 100644 index 00000000000..c57fe08b0ef --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go @@ -0,0 +1,204 @@ +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package softfloat_test + +import ( + "math" + "math/rand" + . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" + "testing" + "runtime" +) + +// turn uint64 op into float64 op +func fop(f func(x, y uint64) uint64) func(x, y float64) float64 { + return func(x, y float64) float64 { + bx := math.Float64bits(x) + by := math.Float64bits(y) + return math.Float64frombits(f(bx, by)) + } +} + +func add(x, y float64) float64 { return x + y } +func sub(x, y float64) float64 { return x - y } +func mul(x, y float64) float64 { return x * y } +func div(x, y float64) float64 { return x / y } + +func TestFloat64(t *testing.T) { + base := []float64{ + 0, + math.Copysign(0, -1), + -1, + 1, + math.NaN(), + math.Inf(+1), + math.Inf(-1), + 0.1, + 1.5, + 1.9999999999999998, // all 1s mantissa + 1.3333333333333333, // 1.010101010101... + 1.1428571428571428, // 1.001001001001... + 1.112536929253601e-308, // first normal + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + 3, + 12, + 1234, + 123456, + -0.1, + -1.5, + -1.9999999999999998, + -1.3333333333333333, + -1.1428571428571428, + -2, + -3, + 1e-200, + 1e-300, + 1e-310, + 5e-324, + 1e-105, + 1e-305, + 1e+200, + 1e+306, + 1e+307, + 1e+308, + } + all := make([]float64, 200) + copy(all, base) + for i := len(base); i < len(all); i++ { + all[i] = rand.NormFloat64() + } + + test(t, "+", add, fop(Fadd64), all) + test(t, "-", sub, fop(Fsub64), all) + if runtime.GOARCH != "386" { // 386 is not precise! + test(t, "*", mul, fop(Fmul64), all) + test(t, "/", div, fop(Fdiv64), all) + } +} + +// 64 -hw-> 32 -hw-> 64 +func trunc32(f float64) float64 { + return float64(float32(f)) +} + +// 64 -sw->32 -hw-> 64 +func to32sw(f float64) float64 { + return float64(math.Float32frombits(F64to32(math.Float64bits(f)))) +} + +// 64 -hw->32 -sw-> 64 +func to64sw(f float64) float64 { + return math.Float64frombits(F32to64(math.Float32bits(float32(f)))) +} + +// float64 -hw-> int64 -hw-> float64 +func hwint64(f float64) float64 { + return float64(int64(f)) +} + +// float64 -hw-> int32 -hw-> float64 +func hwint32(f float64) float64 { + return float64(int32(f)) +} + +// float64 -sw-> int64 -hw-> float64 +func toint64sw(f float64) float64 { + i, ok := F64toint(math.Float64bits(f)) + if !ok { + // There's no right answer for out of range. + // Match the hardware to pass the test. + i = int64(f) + } + return float64(i) +} + +// float64 -hw-> int64 -sw-> float64 +func fromint64sw(f float64) float64 { + return math.Float64frombits(Fintto64(int64(f))) +} + +var nerr int + +func err(t *testing.T, format string, args ...any) { + t.Errorf(format, args...) + + // cut errors off after a while. + // otherwise we spend all our time + // allocating memory to hold the + // formatted output. + if nerr++; nerr >= 10 { + t.Fatal("too many errors") + } +} + +func test(t *testing.T, op string, hw, sw func(float64, float64) float64, all []float64) { + for _, f := range all { + for _, g := range all { + h := hw(f, g) + s := sw(f, g) + if !same(h, s) { + err(t, "%g %s %g = sw %g, hw %g\n", f, op, g, s, h) + } + testu(t, "to32", trunc32, to32sw, h) + testu(t, "to64", trunc32, to64sw, h) + testu(t, "toint64", hwint64, toint64sw, h) + testu(t, "fromint64", hwint64, fromint64sw, h) + testcmp(t, f, h) + testcmp(t, h, f) + testcmp(t, g, h) + testcmp(t, h, g) + } + } +} + +func testu(t *testing.T, op string, hw, sw func(float64) float64, v float64) { + h := hw(v) + s := sw(v) + if !same(h, s) { + err(t, "%s %g = sw %g, hw %g\n", op, v, s, h) + } +} + +func hwcmp(f, g float64) (cmp int, isnan bool) { + switch { + case f < g: + return -1, false + case f > g: + return +1, false + case f == g: + return 0, false + } + return 0, true // must be NaN +} + +func testcmp(t *testing.T, f, g float64) { + hcmp, hisnan := hwcmp(f, g) + scmp, sisnan := Fcmp64(math.Float64bits(f), math.Float64bits(g)) + if int32(hcmp) != scmp || hisnan != sisnan { + err(t, "cmp(%g, %g) = sw %v, %v, hw %v, %v\n", f, g, scmp, sisnan, hcmp, hisnan) + } +} + +func same(f, g float64) bool { + if math.IsNaN(f) && math.IsNaN(g) { + return true + } + if math.Copysign(1, f) != math.Copysign(1, g) { + return false + } + return f == g +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go new file mode 100644 index 00000000000..73f7fa79be0 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go @@ -0,0 +1,89 @@ +// Package softfloat is a copy of the Go runtime's softfloat64.go file. +// It is a pure software floating point implementation. It can be used to +// perform determinstic, hardware-independent floating point computations. +// +// This package uses shortnames to refer to its different operations. Here is a +// quick reference: +// +// add f + g +// sub f - g +// mul f * g +// div f / g +// neg (- f) +// eq f == g +// gt f > g +// ge f >= g +package softfloat + +// This file mostly exports the functions from runtime_softfloat64.go + +//go:generate sh copy.sh + +func Fadd64(f, g uint64) uint64 { return fadd64(f, g) } +func Fsub64(f, g uint64) uint64 { return fsub64(f, g) } +func Fmul64(f, g uint64) uint64 { return fmul64(f, g) } +func Fdiv64(f, g uint64) uint64 { return fdiv64(f, g) } +func Fneg64(f uint64) uint64 { return fneg64(f) } +func Feq64(f, g uint64) bool { return feq64(f, g) } +func Fgt64(f, g uint64) bool { return fgt64(f, g) } +func Fge64(f, g uint64) bool { return fge64(f, g) } + +func Fadd32(f, g uint32) uint32 { return fadd32(f, g) } +func Fsub32(f, g uint32) uint32 { return fadd32(f, Fneg32(g)) } +func Fmul32(f, g uint32) uint32 { return fmul32(f, g) } +func Fdiv32(f, g uint32) uint32 { return fdiv32(f, g) } +func Feq32(f, g uint32) bool { return feq32(f, g) } +func Fgt32(f, g uint32) bool { return fgt32(f, g) } +func Fge32(f, g uint32) bool { return fge32(f, g) } +func Flt32(f, g uint32) bool { + cmp, nan := fcmp64(f32to64(f), f32to64(g)) + return cmp <= -1 && !nan +} + +func Fle32(f, g uint32) bool { + cmp, nan := fcmp64(f32to64(f), f32to64(g)) + return cmp <= 0 && !nan +} + +func Flt64(f, g uint64) bool { + cmp, nan := fcmp64(f, g) + return cmp <= -1 && !nan +} + +func Fle64(f, g uint64) bool { + cmp, nan := fcmp64(f, g) + return cmp <= 0 && !nan +} + +func Fcmp64(f, g uint64) (cmp int32, isnan bool) { return fcmp64(f, g) } + +func Fneg32(f uint32) uint32 { + // Not defined in runtime - this is a copy similar to fneg64. + return f ^ (1 << (mantbits32 + expbits32)) +} + +// Conversions + +func Fintto64(val int64) (f uint64) { return fintto64(val) } +func Fintto32(val int64) (f uint32) { return fintto32(val) } + +func F32to64(f uint32) uint64 { return f32to64(f) } +func F32toint32(x uint32) int32 { return f32toint32(x) } +func F32toint64(x uint32) int64 { return f32toint64(x) } +func F32touint64(x uint32) uint64 { return f32touint64(x) } +func F64to32(f uint64) uint32 { return f64to32(f) } +func F64toint(f uint64) (val int64, ok bool) { return f64toint(f) } +func F64toint32(x uint64) int32 { return f64toint32(x) } +func F64toint64(x uint64) int64 { return f64toint64(x) } +func F64touint64(x uint64) uint64 { return f64touint64(x) } +func Fint32to32(x int32) uint32 { return fint32to32(x) } +func Fint32to64(x int32) uint64 { return fint32to64(x) } +func Fint64to32(x int64) uint32 { return fint64to32(x) } +func Fint64to64(x int64) uint64 { return fint64to64(x) } +func Fuint64to32(x uint64) uint32 { return fuint64to32(x) } +func Fuint64to64(x uint64) uint64 { return fuint64to64(x) } + +// unpack64 unpacks the float64 f into sign, exp, mantissa, isInf, isNaN. + +func Funpack32(f uint32) (sign, mant uint32, exp int, inf, nan bool) { return funpack32(f) } +func Funpack64(f uint64) (sign, mant uint64, exp int, inf, nan bool) { return funpack64(f) } diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 6d26fa7ce54..e99a5505058 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -391,9 +391,9 @@ func isEql(store Store, lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() == rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() == rv.GetFloat32()) // XXX determinism? + return lv.GetFloat32().Eq(rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() == rv.GetFloat64()) // XXX determinism? + return lv.GetFloat64().Eq(rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -532,9 +532,9 @@ func isLss(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() < rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() < rv.GetFloat32()) // XXX determinism? + return lv.GetFloat32().Lt(rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() < rv.GetFloat64()) // XXX determinism? + return lv.GetFloat64().Lt(rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -576,9 +576,9 @@ func isLeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() <= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() <= rv.GetFloat32()) // XXX determinism? + return lv.GetFloat32().Le(rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() <= rv.GetFloat64()) // XXX determinism? + return lv.GetFloat64().Le(rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -620,9 +620,9 @@ func isGtr(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() > rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() > rv.GetFloat32()) // XXX determinism? + return lv.GetFloat32().Gt(rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() > rv.GetFloat64()) // XXX determinism? + return lv.GetFloat64().Gt(rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -664,9 +664,9 @@ func isGeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() >= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() >= rv.GetFloat32()) // XXX determinism? + return lv.GetFloat32().Ge(rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() >= rv.GetFloat64()) // XXX determinism? + return lv.GetFloat64().Ge(rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -714,10 +714,10 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) { lv.SetUint64(lv.GetUint64() + rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() + rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(lv.GetFloat32().Add(rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() + rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(lv.GetFloat64().Add(rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, rv.GetBigInt()) @@ -770,10 +770,10 @@ func subAssign(lv, rv *TypedValue) { lv.SetUint64(lv.GetUint64() - rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() - rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(lv.GetFloat32().Sub(rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() - rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(lv.GetFloat64().Sub(rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, rv.GetBigInt()) @@ -826,10 +826,10 @@ func mulAssign(lv, rv *TypedValue) { lv.SetUint64(lv.GetUint64() * rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() * rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(lv.GetFloat32().Mul(rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() * rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(lv.GetFloat64().Mul(rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Mul(lb, rv.GetBigInt()) @@ -917,18 +917,16 @@ func quoAssign(lv, rv *TypedValue) *Exception { lv.SetUint64(lv.GetUint64() / rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - if rv.GetFloat32() == 0 { + if rv.GetFloat32().Eq(0) { return expt } - lv.SetFloat32(lv.GetFloat32() / rv.GetFloat32()) - // XXX FOR DETERMINISM, PANIC IF NAN. + lv.SetFloat32(lv.GetFloat32().Div(rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - if rv.GetFloat64() == 0 { + if rv.GetFloat64().Eq(0) { return expt } - lv.SetFloat64(lv.GetFloat64() / rv.GetFloat64()) - // XXX FOR DETERMINISM, PANIC IF NAN. + lv.SetFloat64(lv.GetFloat64().Div(rv.GetFloat64())) case BigintType, UntypedBigintType: if rv.GetBigInt().Sign() == 0 { return expt diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 7a8a885bcf0..98583a57976 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -54,9 +54,9 @@ func (m *Machine) doOpInc() { case Uint64Type: lv.SetUint64(lv.GetUint64() + 1) case Float32Type: - lv.SetFloat32(lv.GetFloat32() + 1) + lv.SetFloat32(lv.GetFloat32().Add(1)) case Float64Type: - lv.SetFloat64(lv.GetFloat64() + 1) + lv.SetFloat64(lv.GetFloat64().Add(1)) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, big.NewInt(1)) @@ -124,9 +124,9 @@ func (m *Machine) doOpDec() { case Uint64Type: lv.SetUint64(lv.GetUint64() - 1) case Float32Type: - lv.SetFloat32(lv.GetFloat32() - 1) + lv.SetFloat32(lv.GetFloat32().Sub(1)) case Float64Type: - lv.SetFloat64(lv.GetFloat64() - 1) + lv.SetFloat64(lv.GetFloat64().Sub(1)) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, big.NewInt(1)) diff --git a/gnovm/pkg/gnolang/op_unary.go b/gnovm/pkg/gnolang/op_unary.go index 9c330c7f8f1..99ecb98d792 100644 --- a/gnovm/pkg/gnolang/op_unary.go +++ b/gnovm/pkg/gnolang/op_unary.go @@ -46,9 +46,9 @@ func (m *Machine) doOpUneg() { case Uint64Type: xv.SetUint64(-xv.GetUint64()) case Float32Type: - xv.SetFloat32(-xv.GetFloat32()) + xv.SetFloat32(xv.GetFloat32().Neg()) case Float64Type: - xv.SetFloat64(-xv.GetFloat64()) + xv.SetFloat64(xv.GetFloat64().Neg()) case UntypedBigintType, BigintType: bv := xv.V.(BigintValue) xv.V = BigintValue{V: new(big.Int).Neg(bv.V)} diff --git a/gnovm/pkg/gnolang/softfloat.go b/gnovm/pkg/gnolang/softfloat.go new file mode 100644 index 00000000000..342ce493e8a --- /dev/null +++ b/gnovm/pkg/gnolang/softfloat.go @@ -0,0 +1,338 @@ +package gnolang + +import ( + "fmt" + "math" + + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" +) + +const ( + mask = 0x7FF + shift = 64 - 11 - 1 + bias = 1023 +) + +type ( + SoftFloat64 uint64 + SoftFloat32 uint32 +) + +func Trunc(x SoftFloat64) SoftFloat64 { + cmp, _ := softfloat.Fcmp64(uint64(x), softfloat.Fintto64(0)) + if _, _, _, isInf, IsNaN := softfloat.Funpack64(uint64(x)); cmp == 0 || isInf || IsNaN { + return x + } + + d, _ := Modf(x) + return d +} + +func Modf(u SoftFloat64) (it SoftFloat64, frac SoftFloat64) { + if u.Lt(1) { + switch { + case u.Lt(0): + it, frac = Modf(u.Neg()) + return -it, -frac + case u.Eq(0): + return u, u // Return -0, -0 when f == -0 + } + return 0, u + } + + it = u + e := uint(it>>shift)&mask - bias + + // Keep the top 12+e bits, the integer part; clear the rest. + if e < 64-12 { + it &^= 1<<(64-12-e) - 1 + } + + frac = u.Sub(it) + return +} + +func ConvertToSoftFloat64(n any) SoftFloat64 { + switch n := n.(type) { + case SoftFloat64: + return n + case SoftFloat32: + return SoftFloat64(softfloat.F32to64(uint32(n))) + case int: + return SoftFloat64(softfloat.Fintto64(int64(n))) + case int32: + return SoftFloat64(softfloat.Fint32to64(n)) + case int8: + return SoftFloat64(softfloat.Fint32to64(int32(n))) + case int16: + return SoftFloat64(softfloat.Fint32to64(int32(n))) + case int64: + return SoftFloat64(softfloat.Fint64to64(n)) + case uint: + return SoftFloat64(softfloat.Fuint64to64(uint64(n))) + case uint16: + return SoftFloat64(softfloat.Fuint64to64(uint64(n))) + case uint32: + return SoftFloat64(softfloat.Fuint64to64(uint64(n))) + case uint8: + return SoftFloat64(softfloat.Fuint64to64(uint64(n))) + case uint64: + return SoftFloat64(softfloat.Fuint64to64(n)) + case float32: + return SoftFloat32(math.Float32bits(n)).SoftFloat64() + case float64: + return SoftFloat64(math.Float64bits(n)) + default: + panic(fmt.Sprintf("unsupported type: %T", n)) + } +} + +func ConvertToSoftFloat32(n any) SoftFloat32 { + switch n := n.(type) { + case SoftFloat64: + return SoftFloat32(softfloat.F64to32(uint64(n))) + case SoftFloat32: + return n + case int: + return SoftFloat32(softfloat.Fintto32(int64(n))) + case int32: + return SoftFloat32(softfloat.Fint32to32(n)) + case int8: + return SoftFloat32(softfloat.Fint32to32(int32(n))) + case int16: + return SoftFloat32(softfloat.Fint32to32(int32(n))) + case int64: + return SoftFloat32(softfloat.Fint64to32(n)) + case uint: + return SoftFloat32(softfloat.Fuint64to32(uint64(n))) + case uint16: + return SoftFloat32(softfloat.Fuint64to32(uint64(n))) + case uint32: + return SoftFloat32(softfloat.Fuint64to32(uint64(n))) + case uint8: + return SoftFloat32(softfloat.Fuint64to32(uint64(n))) + case uint64: + return SoftFloat32(softfloat.Fuint64to32(n)) + case float32: + return SoftFloat32(math.Float32bits(n)) + case float64: + return SoftFloat64(math.Float64bits(n)).SoftFloat32() + default: + panic(fmt.Sprintf("unsupported type: %T", n)) + } +} + +// SoftFloat64 + +func (f SoftFloat64) String() string { + return fmt.Sprintf("%v", math.Float64frombits(uint64(f))) +} + +func (f SoftFloat64) Float64() float64 { + return math.Float64frombits(uint64(f)) +} + +func (f SoftFloat64) Float32() float32 { + return float32(math.Float64frombits(uint64(f))) +} + +func (f SoftFloat64) SoftFloat32() SoftFloat32 { + return SoftFloat32(softfloat.F64to32(uint64(f))) +} + +func (f SoftFloat64) Int() int { + n, _ := softfloat.F64toint(uint64(f)) + return int(n) +} + +func (f SoftFloat64) Int64() int64 { + return softfloat.F64toint64(uint64(f)) +} + +func (f SoftFloat64) Int32() int32 { + return softfloat.F64toint32(uint64(f)) +} + +func (f SoftFloat64) Int16() int16 { + return int16(softfloat.F64toint32(uint64(f))) +} + +func (f SoftFloat64) Int8() int8 { + return int8(softfloat.F64toint32(uint64(f))) +} + +func (f SoftFloat64) Uint() uint { + return uint(softfloat.F64touint64(uint64(f))) +} + +func (f SoftFloat64) Uint64() uint64 { + return softfloat.F64touint64(uint64(f)) +} + +func (f SoftFloat64) Uint32() uint32 { + return uint32(softfloat.F64touint64(uint64(f))) +} + +func (f SoftFloat64) Uint16() uint16 { + return uint16(softfloat.F64touint64(uint64(f))) +} + +func (f SoftFloat64) Uint8() uint8 { + return uint8(softfloat.F64touint64(uint64(f))) +} + +func (f SoftFloat64) Add(g any) SoftFloat64 { + return SoftFloat64(softfloat.Fadd64(uint64(f), uint64(ConvertToSoftFloat64(g)))) +} + +func (f SoftFloat64) Sub(g any) SoftFloat64 { + return SoftFloat64(softfloat.Fsub64(uint64(f), uint64(ConvertToSoftFloat64(g)))) +} + +func (f SoftFloat64) Mul(g any) SoftFloat64 { + return SoftFloat64(softfloat.Fmul64(uint64(f), uint64(ConvertToSoftFloat64(g)))) +} + +func (f SoftFloat64) Div(g any) SoftFloat64 { + return SoftFloat64(softfloat.Fdiv64(uint64(f), uint64(ConvertToSoftFloat64(g)))) +} + +func (f SoftFloat64) Neg() SoftFloat64 { + return SoftFloat64(softfloat.Fneg64(uint64(f))) +} + +func (f SoftFloat64) Trunc() SoftFloat64 { + return Trunc(f) +} + +// == +func (f SoftFloat64) Eq(g any) bool { + return softfloat.Feq64(uint64(f), uint64(ConvertToSoftFloat64(g))) +} + +// > +func (f SoftFloat64) Gt(g any) bool { + return softfloat.Fgt64(uint64(f), uint64(ConvertToSoftFloat64(g))) +} + +// >= +func (f SoftFloat64) Ge(g any) bool { + return softfloat.Fge64(uint64(f), uint64(ConvertToSoftFloat64(g))) +} + +// < +func (f SoftFloat64) Lt(g any) bool { + return softfloat.Flt64(uint64(f), uint64(ConvertToSoftFloat64(g))) +} + +// <= +func (f SoftFloat64) Le(g any) bool { + return softfloat.Fle64(uint64(f), uint64(ConvertToSoftFloat64(g))) +} + +// SoftFloat32 + +func (f SoftFloat32) Float32() float32 { + return math.Float32frombits(uint32(f)) +} + +func (f SoftFloat32) Float64() float64 { + return math.Float64frombits(softfloat.F32to64(uint32(f))) +} + +func (f SoftFloat32) SoftFloat64() SoftFloat64 { + return SoftFloat64(softfloat.F32to64(uint32(f))) +} + +func (f SoftFloat32) Int() int { + return int(softfloat.F32toint64(uint32(f))) +} + +func (f SoftFloat32) Int64() int64 { + return softfloat.F32toint64(uint32(f)) +} + +func (f SoftFloat32) Int32() int32 { + return softfloat.F32toint32(uint32(f)) +} + +func (f SoftFloat32) Int16() int16 { + return int16(softfloat.F32toint32(uint32(f))) +} + +func (f SoftFloat32) Int8() int8 { + return int8(softfloat.F32toint32(uint32(f))) +} + +func (f SoftFloat32) Uint() uint { + return uint(softfloat.F32touint64(uint32(f))) +} + +func (f SoftFloat32) Uint64() uint64 { + return softfloat.F32touint64(uint32(f)) +} + +func (f SoftFloat32) Uint32() uint32 { + return uint32(softfloat.F32touint64(uint32(f))) +} + +func (f SoftFloat32) Uint16() uint16 { + return uint16(softfloat.F32touint64(uint32(f))) +} + +func (f SoftFloat32) Uint8() uint8 { + return uint8(softfloat.F32touint64(uint32(f))) +} + +func (f SoftFloat32) Add(g any) SoftFloat32 { + return SoftFloat32(softfloat.Fadd32(uint32(f), uint32(ConvertToSoftFloat32(g)))) +} + +func (f SoftFloat32) Sub(g any) SoftFloat32 { + return SoftFloat32(softfloat.Fsub32(uint32(f), uint32(ConvertToSoftFloat32(g)))) +} + +func (f SoftFloat32) Mul(g any) SoftFloat32 { + return SoftFloat32(softfloat.Fmul32(uint32(f), uint32(ConvertToSoftFloat32(g)))) +} + +func (f SoftFloat32) Div(g any) SoftFloat32 { + return SoftFloat32(softfloat.Fdiv32(uint32(f), uint32(ConvertToSoftFloat32(g)))) +} + +func (f SoftFloat32) Neg() SoftFloat32 { + return SoftFloat32(softfloat.Fneg32(uint32(f))) +} + +func (f SoftFloat32) Trunc() SoftFloat32 { + return SoftFloat32(softfloat.F64to32(uint64(Trunc(SoftFloat64(softfloat.F32to64(uint32(f))))))) +} + +// == +func (f SoftFloat32) Eq(g any) bool { + return softfloat.Feq32(uint32(f), uint32(ConvertToSoftFloat32(g))) +} + +// > +func (f SoftFloat32) Gt(g any) bool { + return softfloat.Fgt32(uint32(f), uint32(ConvertToSoftFloat32(g))) +} + +// >= +func (f SoftFloat32) Ge(g any) bool { + return softfloat.Fge32(uint32(f), uint32(ConvertToSoftFloat32(g))) +} + +// < +func (f SoftFloat32) Lt(g any) bool { + return softfloat.Flt32(uint32(f), uint32(ConvertToSoftFloat32(g))) +} + +// <= +func (f SoftFloat32) Le(g any) bool { + return softfloat.Fle32(uint32(f), uint32(ConvertToSoftFloat32(g))) +} + +func (f SoftFloat32) String() string { + return fmt.Sprintf("%v", math.Float32frombits(uint32(f))) +} diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 4c2e2835f95..84e2882b519 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -3,7 +3,6 @@ package gnolang import ( "encoding/binary" "fmt" - "math" "math/big" "reflect" "strconv" @@ -1121,15 +1120,15 @@ func (tv *TypedValue) PrimitiveBytes() (data []byte) { return data case Float32Type: data = make([]byte, 4) - u32 := math.Float32bits(tv.GetFloat32()) + u32 := tv.GetFloat32() binary.LittleEndian.PutUint32( - data, u32) + data, uint32(u32)) return data case Float64Type: data = make([]byte, 8) - u64 := math.Float64bits(tv.GetFloat64()) + u64 := tv.GetFloat64() binary.LittleEndian.PutUint64( - data, u64) + data, uint64(u64)) return data case BigintType: return tv.V.(BigintValue).V.Bytes() @@ -1450,7 +1449,7 @@ func (tv *TypedValue) GetUint64() uint64 { return *(*uint64)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat32(n float32) { +func (tv *TypedValue) SetFloat32(n SoftFloat32) { if debug { if tv.T.Kind() != Float32Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1458,10 +1457,10 @@ func (tv *TypedValue) SetFloat32(n float32) { tv.T.String())) } } - *(*float32)(unsafe.Pointer(&tv.N)) = n + *(*SoftFloat32)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat32() float32 { +func (tv *TypedValue) GetFloat32() SoftFloat32 { if debug { if tv.T != nil && tv.T.Kind() != Float32Kind { panic(fmt.Sprintf( @@ -1469,10 +1468,10 @@ func (tv *TypedValue) GetFloat32() float32 { tv.T.String())) } } - return *(*float32)(unsafe.Pointer(&tv.N)) + return *(*SoftFloat32)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat64(n float64) { +func (tv *TypedValue) SetFloat64(n SoftFloat64) { if debug { if tv.T.Kind() != Float64Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1480,10 +1479,10 @@ func (tv *TypedValue) SetFloat64(n float64) { tv.T.String())) } } - *(*float64)(unsafe.Pointer(&tv.N)) = n + *(*SoftFloat64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat64() float64 { +func (tv *TypedValue) GetFloat64() SoftFloat64 { if debug { if tv.T != nil && tv.T.Kind() != Float64Kind { panic(fmt.Sprintf( @@ -1491,7 +1490,7 @@ func (tv *TypedValue) GetFloat64() float64 { tv.T.String())) } } - return *(*float64)(unsafe.Pointer(&tv.N)) + return *(*SoftFloat64)(unsafe.Pointer(&tv.N)) } func (tv *TypedValue) GetBigInt() *big.Int { diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index baeded76c1a..09963185b58 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -163,11 +163,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetInt()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetInt()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -233,11 +233,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt8()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetInt8()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt8()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetInt8()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -304,11 +304,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt16()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetInt16()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt16()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetInt16()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -379,11 +379,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt32()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetInt32()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt32()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetInt32()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -456,11 +456,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt64()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetInt64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt64()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetInt64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -533,11 +533,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetUint()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetUint()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -602,11 +602,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint8()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetUint8()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint8()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetUint8()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -673,11 +673,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint16()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetUint16()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint16()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetUint16()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -746,11 +746,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint32()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetUint32()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint32()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetUint32()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -825,11 +825,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint64()) // XXX determinism? + x := ConvertToSoftFloat32(tv.GetUint64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint64()) // XXX determinism? + x := ConvertToSoftFloat64(tv.GetUint64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -847,156 +847,155 @@ GNO_CASE: switch k { case IntKind: validate(Float32Kind, IntKind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Int() tv.T = t tv.SetInt(x) case Int8Kind: validate(Float32Kind, Int8Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8 }) - x := int8(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Int8() tv.T = t tv.SetInt8(x) case Int16Kind: validate(Float32Kind, Int16Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16 }) - x := int16(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Int16() tv.T = t tv.SetInt16(x) case Int32Kind: validate(Float32Kind, Int32Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32 }) - x := int32(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Int32() tv.T = t tv.SetInt32(x) case Int64Kind: validate(Float32Kind, Int64Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - return val == trunc + return tv.GetFloat32().Eq(trunc) }) - x := int64(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Int64() tv.T = t tv.SetInt64(x) case UintKind: validate(Float32Kind, UintKind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Uint() tv.T = t tv.SetUint(x) case Uint8Kind: validate(Float32Kind, Uint8Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint8 }) - x := uint8(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Uint8() tv.T = t tv.SetUint8(x) case Uint16Kind: validate(Float32Kind, Uint16Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint16 }) - x := uint16(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Uint16() tv.T = t tv.SetUint16(x) case Uint32Kind: validate(Float32Kind, Uint32Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint32 }) - x := uint32(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Uint32() tv.T = t tv.SetUint32(x) case Uint64Kind: validate(Float32Kind, Uint64Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := tv.GetFloat32().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat32()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint64(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().Uint64() tv.T = t tv.SetUint64(x) case Float32Kind: - x := tv.GetFloat32() // XXX determinism? + x := tv.GetFloat32() // ??? tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetFloat32()) // XXX determinism? + x := tv.GetFloat32().SoftFloat64() /// ??? tv.T = t tv.SetFloat64(x) default: @@ -1008,160 +1007,160 @@ GNO_CASE: switch k { case IntKind: validate(Float64Kind, IntKind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Int() tv.T = t tv.SetInt(x) case Int8Kind: validate(Float64Kind, Int8Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8 }) - x := int8(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Int8() tv.T = t tv.SetInt8(x) case Int16Kind: validate(Float64Kind, Int16Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16 }) - x := int16(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Int16() tv.T = t tv.SetInt16(x) case Int32Kind: validate(Float64Kind, Int32Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 + truncInt64 := trunc.Int64() + return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32 }) - x := int32(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Int32() tv.T = t tv.SetInt32(x) case Int64Kind: validate(Float64Kind, Int64Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - return val == trunc + return tv.GetFloat64().Eq(trunc) }) - x := int64(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Int64() tv.T = t tv.SetInt64(x) case UintKind: validate(Float64Kind, UintKind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := trunc.Uint64() + + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Uint() tv.T = t tv.SetUint(x) case Uint8Kind: validate(Float64Kind, Uint8Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint8 }) - x := uint8(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Uint8() tv.T = t tv.SetUint8(x) case Uint16Kind: validate(Float64Kind, Uint16Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint16 }) - x := uint16(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Uint16() tv.T = t tv.SetUint16(x) case Uint32Kind: validate(Float64Kind, Uint32Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint32 }) - x := uint32(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Uint32() tv.T = t tv.SetUint32(x) case Uint64Kind: validate(Float64Kind, Uint64Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := tv.GetFloat64().Trunc() - if val != trunc { + if !trunc.Eq(tv.GetFloat64()) { return false } - return trunc >= 0 && trunc <= math.MaxUint64 + truncUint64 := trunc.Uint64() + return truncUint64 >= 0 && truncUint64 <= math.MaxUint64 }) - x := uint64(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().Uint64() tv.T = t tv.SetUint64(x) case Float32Kind: validate(Float64Kind, Float32Kind, func() bool { - return tv.GetFloat64() <= math.MaxFloat32 + return tv.GetFloat64().Le(SoftFloat64(math.Float64bits(float64(math.MaxFloat32)))) }) - x := float32(tv.GetFloat64()) // XXX determinism? + x := tv.GetFloat64().SoftFloat32() tv.T = t tv.SetFloat32(x) case Float64Kind: - x := tv.GetFloat64() // XXX determinism? + x := tv.GetFloat64() // ??? tv.T = t tv.SetFloat64(x) default: @@ -1481,7 +1480,7 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if f32 == 0 && (acc == big.Below || acc == big.Above) { panic("bigint underflows float32 (too close to zero)") } - dst.SetFloat32(f32) + dst.SetFloat32(SoftFloat32(math.Float32bits(f32))) return // done case Float64Kind: dst.T = t @@ -1495,7 +1494,7 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if f64 == 0 && (acc == big.Below || acc == big.Above) { panic("bigint underflows float64 (too close to zero)") } - dst.SetFloat64(f64) + dst.SetFloat64(SoftFloat64(math.Float64bits(f64))) return // done case BigdecKind: dst.T = t @@ -1610,7 +1609,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { dst.T = Float64Type dst.V = nil f, _ := bd.Float64() - dst.SetFloat64(f) + dst.SetFloat64(SoftFloat64(math.Float64bits(f))) return case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind: fallthrough @@ -1636,7 +1635,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { if math.IsInf(float64(f32), 0) { panic("cannot convert untyped bigdec to float32 -- too close to +-Inf") } - dst.SetFloat32(f32) + dst.SetFloat32(SoftFloat32(math.Float32bits(f32))) return case Float64Kind: dst.T = t @@ -1648,7 +1647,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { if math.IsInf(f64, 0) { panic("cannot convert untyped bigdec to float64 -- too close to +-Inf") } - dst.SetFloat64(f64) + dst.SetFloat64(SoftFloat64(math.Float64bits(f64))) return default: panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go index 7ffa3e98c71..2899fabda3d 100644 --- a/gnovm/pkg/gnolang/values_conversions_test.go +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -24,7 +24,7 @@ func TestConvertUntypedBigdecToFloat(t *testing.T) { ConvertUntypedBigdecTo(dst, bd, typ) - require.Equal(t, float64(0), dst.GetFloat64()) + require.Equal(t, ConvertToSoftFloat64(0), dst.GetFloat64()) } func TestBitShiftingOverflow(t *testing.T) { diff --git a/gnovm/tests/files/float8.gno b/gnovm/tests/files/float8.gno new file mode 100644 index 00000000000..e1fce9bb3d7 --- /dev/null +++ b/gnovm/tests/files/float8.gno @@ -0,0 +1,79 @@ +package main + +import "math" + +func main() { + var i8 int8 = 127 + var i16 int16 = 32767 + var i32 int32 = 2147483647 + var i64 int64 = 9223372036854775807 + var i int = 9223372036854775807 + var u8 uint8 = 255 + var u16 uint16 = 65535 + var u32 uint32 = 4294967295 + var u64 uint64 = 18446744073709551615 + var f32 float32 = math.MaxFloat32 + var f64 float64 = math.MaxFloat64 + println(f32 / 2) + println(f64 / 2) + println((f32 - 1) + 1) + println((f64 - 1) + 1) + println((f32 / 2) * 2) + println((f64 / 2) * 2) + println(f32 - 1) + println(f64 - 1) + println(float32(i8)) + println(float64(i8)) + println(float32(i16)) + println(float64(i16)) + println(float32(i32)) + println(float64(i32)) + println(float32(i64)) + println(float64(i64)) + println(float32(i)) + println(float64(i)) + println(float32(u8)) + println(float64(u8)) + println(float32(u16)) + println(float64(u16)) + println(float32(u32)) + println(float64(u32)) + println(float32(u64)) + println(float64(u64)) + println(float32(f32)) + println(float64(f32)) + println(float32(f64)) + println(float64(f64)) +} + +// Output: +// 1.7014117e+38 +// 8.988465674311579e+307 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 127 +// 127 +// 32767 +// 32767 +// 2.1474836e+09 +// 2.147483647e+09 +// 9.223372e+18 +// 9.223372036854776e+18 +// 9.223372e+18 +// 9.223372036854776e+18 +// 255 +// 255 +// 65535 +// 65535 +// 4.2949673e+09 +// 4.294967295e+09 +// 1.8446744e+19 +// 1.8446744073709552e+19 +// 3.4028235e+38 +// 3.4028234663852886e+38 +// +Inf +// 1.7976931348623157e+308