diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index fe92f5bcd23..41ff092a417 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -2,7 +2,10 @@ package gnolang import ( "fmt" + "math" "reflect" + + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // NOTE @@ -379,9 +382,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(softfloat.Float32(math.Float32bits(float32(rv.Float())))) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(softfloat.Float64(math.Float64bits(rv.Float()))) case reflect.Array: tv.V = alloc.NewNative(rv) case reflect.Slice: @@ -478,11 +481,11 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case Float32Kind: if lvl != 0 { - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(softfloat.Float32(math.Float32bits(float32(rv.Float())))) } case Float64Kind: if lvl != 0 { - tv.SetFloat64(rv.Float()) + tv.SetFloat64(softfloat.Float64(math.Float64bits(rv.Float()))) } case BigintKind: panic("not yet implemented") @@ -694,9 +697,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(softfloat.Float32(math.Float32bits(float32(rv.Float())))) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(softfloat.Float64(math.Float64bits(rv.Float()))) case reflect.Array: rvl := rv.Len() if rv.Type().Elem().Kind() == reflect.Uint8 { @@ -1099,9 +1102,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(math.Float64frombits(uint64(tv.GetFloat32()))) case Float64Type: - rv.SetFloat(tv.GetFloat64()) + rv.SetFloat(math.Float64frombits(uint64(tv.GetFloat64()))) default: panic(fmt.Sprintf( "unexpected type %s", diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go index c57fe08b0ef..d746df489f8 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go @@ -18,11 +18,11 @@ import ( ) // turn uint64 op into float64 op -func fop(f func(x, y uint64) uint64) func(x, y float64) float64 { +func fop(f func(x, y Float64) Float64) 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)) + return math.Float64frombits(uint64(f(Float64(bx), Float64(by)))) } } @@ -97,12 +97,12 @@ func trunc32(f float64) float64 { // 64 -sw->32 -hw-> 64 func to32sw(f float64) float64 { - return float64(math.Float32frombits(F64to32(math.Float64bits(f)))) + return float64(math.Float32frombits(uint32(F64to32(Float64(math.Float64bits(f)))))) } // 64 -hw->32 -sw-> 64 func to64sw(f float64) float64 { - return math.Float64frombits(F32to64(math.Float32bits(float32(f)))) + return math.Float64frombits(uint64(F32to64(Float32(math.Float32bits(float32(f)))))) } // float64 -hw-> int64 -hw-> float64 @@ -117,7 +117,7 @@ func hwint32(f float64) float64 { // float64 -sw-> int64 -hw-> float64 func toint64sw(f float64) float64 { - i, ok := F64toint(math.Float64bits(f)) + i, ok := F64toint(Float64(math.Float64bits(f))) if !ok { // There's no right answer for out of range. // Match the hardware to pass the test. @@ -128,7 +128,7 @@ func toint64sw(f float64) float64 { // float64 -hw-> int64 -sw-> float64 func fromint64sw(f float64) float64 { - return math.Float64frombits(Fintto64(int64(f))) + return math.Float64frombits(uint64(Fintto64(int64(f)))) } var nerr int @@ -187,7 +187,7 @@ func hwcmp(f, g float64) (cmp int, isnan bool) { func testcmp(t *testing.T, f, g float64) { hcmp, hisnan := hwcmp(f, g) - scmp, sisnan := Fcmp64(math.Float64bits(f), math.Float64bits(g)) + scmp, sisnan := Fcmp64(Float64(math.Float64bits(f)), Float64(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) } diff --git a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go index 96ac86f6053..4b1fb7ad20c 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go +++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go @@ -15,28 +15,72 @@ // ge f >= g package softfloat +const ( + mask = 0x7FF + shift = 64 - 11 - 1 + bias = 1023 +) + +type Float64 uint64 +type Float32 uint32 + +func Trunc(x Float64) Float64 { + _, _, _, isInf, IsNaN := funpack64(uint64(x)) + if x == 0 || isInf || IsNaN { + return x + } + + d, _ := Modf(x) + return d +} +func Modf(u Float64) (int Float64, frac Float64) { + cmp, _ := Fcmp64(u, 1) + + if cmp < 0 { + cmp, _ := Fcmp64(u, 0) + switch { + case cmp < 0: + int, frac = Modf(Fneg64(u)) + return Fneg64(int), Fneg64(frac) + case cmp == 0: + return u, u // Return -0, -0 when f == -0 + } + return 0, u + } + + e := uint(u>>shift)&mask - bias + + // Keep the top 12+e bits, the integer part; clear the rest. + if e < 64-12 { + u &^= 1<<(64-12-e) - 1 + } + + frac = Fsub64(u, int) + return +} + // 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 Fcmp64(f, g uint64) (cmp int32, isnan bool) { return fcmp64(f, g) } +func Fadd64(f, g Float64) Float64 { return Float64(fadd64(uint64(f), uint64(g))) } +func Fsub64(f, g Float64) Float64 { return Float64(fsub64(uint64(f), uint64(g))) } +func Fmul64(f, g Float64) Float64 { return Float64(fmul64(uint64(f), uint64(g))) } +func Fdiv64(f, g Float64) Float64 { return Float64(fdiv64(uint64(f), uint64(g))) } +func Fneg64(f Float64) Float64 { return Float64(fneg64(uint64(f))) } +func Feq64(f, g Float64) bool { return feq64(uint64(f), uint64(g)) } +func Fgt64(f, g Float64) bool { return fgt64(uint64(f), uint64(g)) } +func Fge64(f, g Float64) bool { return fge64(uint64(f), uint64(g)) } + +func Fadd32(f, g Float32) Float32 { return Float32(fadd32(uint32(f), uint32(g))) } +func Fsub32(f, g Float32) Float32 { return Float32(fadd32(uint32(f), uint32(Fneg32(uint32(g))))) } +func Fmul32(f, g Float32) Float32 { return Float32(fmul32(uint32(f), uint32(g))) } +func Fdiv32(f, g Float32) Float32 { return Float32(fdiv32(uint32(f), uint32(g))) } +func Feq32(f, g Float32) bool { return feq32(uint32(f), uint32(g)) } +func Fgt32(f, g Float32) bool { return fgt32(uint32(f), uint32(g)) } +func Fge32(f, g Float32) bool { return fge32(uint32(f), uint32(g)) } + +func Fcmp64(f, g Float64) (cmp int32, isnan bool) { return fcmp64(uint64(f), uint64(g)) } func Fneg32(f uint32) uint32 { // Not defined in runtime - this is a copy similar to fneg64. @@ -45,19 +89,21 @@ func Fneg32(f uint32) uint32 { // 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 Fintto64(val int64) Float64 { return Float64(fintto64(val)) } +func Fintto32(val int64) Float32 { return Float32(fintto32(val)) } + +func F32to64(f Float32) Float64 { return Float64(f32to64(uint32(f))) } +func F32toint32(x Float32) int32 { return f32toint32(uint32(x)) } +func F32toint64(x Float32) int64 { return f32toint64(uint32(x)) } +func F32touint64(x Float32) uint64 { return f32touint64(uint32(x)) } +func F64to32(f Float64) Float32 { return Float32(f64to32(uint64(f))) } +func F64toint(f Float64) (val int64, ok bool) { return f64toint(uint64(f)) } +func F64toint32(x Float64) int32 { return f64toint32(uint64(x)) } +func F64toint64(x Float64) int64 { return f64toint64(uint64(x)) } +func F64touint64(x Float64) uint64 { return f64touint64(uint64(x)) } +func Fint32to32(x int32) Float32 { return Float32(fint32to32(x)) } +func Fint32to64(x int32) Float64 { return Float64(fint32to64(x)) } +func Fint64to32(x int64) Float32 { return Float32(fint64to32(x)) } +func Fint64to64(x int64) Float64 { return Float64(fint64to64(x)) } +func Fuint64to32(x uint64) Float32 { return Float32(fuint64to32(x)) } +func Fuint64to64(x uint64) Float64 { return Float64(fuint64to64(x)) } diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index a541a7da8b5..69c011316f8 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // ---------------------------------------- @@ -390,9 +391,11 @@ func isEql(store Store, lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() == rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() == rv.GetFloat32()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(softfloat.Float64(lv.GetFloat32()), softfloat.Float64(rv.GetFloat32())) + return cmp == 0 && !nan case Float64Kind: - return (lv.GetFloat64() == rv.GetFloat64()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(lv.GetFloat64(), rv.GetFloat64()) + return cmp == 0 && !nan case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -531,9 +534,11 @@ func isLss(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() < rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() < rv.GetFloat32()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(softfloat.Float64(lv.GetFloat32()), softfloat.Float64(rv.GetFloat32())) + return (cmp < 0) && !nan case Float64Kind: - return (lv.GetFloat64() < rv.GetFloat64()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(lv.GetFloat64(), rv.GetFloat64()) + return (cmp < 0) && !nan case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -575,9 +580,11 @@ func isLeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() <= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() <= rv.GetFloat32()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(softfloat.Float64(lv.GetFloat32()), softfloat.Float64(rv.GetFloat32())) + return (cmp <= 0) && !nan case Float64Kind: - return (lv.GetFloat64() <= rv.GetFloat64()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(lv.GetFloat64(), rv.GetFloat64()) + return (cmp <= 0) && !nan case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -619,9 +626,11 @@ func isGtr(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() > rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() > rv.GetFloat32()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(softfloat.Float64(lv.GetFloat32()), softfloat.Float64(rv.GetFloat32())) + return (cmp > 0) && !nan case Float64Kind: - return (lv.GetFloat64() > rv.GetFloat64()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(lv.GetFloat64(), rv.GetFloat64()) + return (cmp > 0) && !nan case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -663,9 +672,11 @@ func isGeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() >= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() >= rv.GetFloat32()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(softfloat.Float64(lv.GetFloat32()), softfloat.Float64(rv.GetFloat32())) + return (cmp >= 0) && !nan case Float64Kind: - return (lv.GetFloat64() >= rv.GetFloat64()) // XXX determinism? + cmp, nan := softfloat.Fcmp64(lv.GetFloat64(), rv.GetFloat64()) + return (cmp >= 0) && !nan case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -713,10 +724,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(softfloat.Fadd32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() + rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, rv.GetBigInt()) @@ -769,10 +780,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(softfloat.Fsub32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() - rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, rv.GetBigInt()) @@ -825,10 +836,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(softfloat.Fmul32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() * rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fmul64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Mul(lb, rv.GetBigInt()) @@ -916,18 +927,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 cmp, nan := softfloat.Fcmp64(softfloat.F32to64(rv.GetFloat32()), softfloat.Fint32to64(0)); cmp == 0 && !nan { return expt } - lv.SetFloat32(lv.GetFloat32() / rv.GetFloat32()) - // XXX FOR DETERMINISM, PANIC IF NAN. + lv.SetFloat32(softfloat.Fdiv32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - if rv.GetFloat64() == 0 { + if cmp, nan := softfloat.Fcmp64(rv.GetFloat64(), 0); cmp == 0 && !nan { return expt } - lv.SetFloat64(lv.GetFloat64() / rv.GetFloat64()) - // XXX FOR DETERMINISM, PANIC IF NAN. + lv.SetFloat64(softfloat.Fdiv64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: if rv.GetBigInt().Sign() == 0 { return expt diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 8e27bcbcbdb..1a8badf3444 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" @@ -12,6 +11,7 @@ import ( "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/gnolang/gno/tm2/pkg/crypto" ) @@ -1121,15 +1121,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 +1450,7 @@ func (tv *TypedValue) GetUint64() uint64 { return *(*uint64)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat32(n float32) { +func (tv *TypedValue) SetFloat32(n softfloat.Float32) { if debug { if tv.T.Kind() != Float32Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1458,10 +1458,10 @@ func (tv *TypedValue) SetFloat32(n float32) { tv.T.String())) } } - *(*float32)(unsafe.Pointer(&tv.N)) = n + *(*softfloat.Float32)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat32() float32 { +func (tv *TypedValue) GetFloat32() softfloat.Float32 { if debug { if tv.T != nil && tv.T.Kind() != Float32Kind { panic(fmt.Sprintf( @@ -1469,10 +1469,10 @@ func (tv *TypedValue) GetFloat32() float32 { tv.T.String())) } } - return *(*float32)(unsafe.Pointer(&tv.N)) + return *(*softfloat.Float32)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat64(n float64) { +func (tv *TypedValue) SetFloat64(n softfloat.Float64) { if debug { if tv.T.Kind() != Float64Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1480,10 +1480,10 @@ func (tv *TypedValue) SetFloat64(n float64) { tv.T.String())) } } - *(*float64)(unsafe.Pointer(&tv.N)) = n + *(*softfloat.Float64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat64() float64 { +func (tv *TypedValue) GetFloat64() softfloat.Float64 { if debug { if tv.T != nil && tv.T.Kind() != Float64Kind { panic(fmt.Sprintf( @@ -1491,7 +1491,7 @@ func (tv *TypedValue) GetFloat64() float64 { tv.T.String())) } } - return *(*float64)(unsafe.Pointer(&tv.N)) + return *(*softfloat.Float64)(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 df93144b4e7..998286f2293 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // t cannot be nil or untyped or DataByteType. @@ -163,11 +164,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt()) // XXX determinism? + x := softfloat.Fintto32(int64(tv.GetInt())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt()) // XXX determinism? + x := softfloat.Fintto64(int64(tv.GetInt())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -233,11 +234,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt8()) // XXX determinism? + x := softfloat.Fint32to32(int32(tv.GetInt8())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt8()) // XXX determinism? + x := softfloat.Fint32to64(int32(tv.GetInt8())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -304,11 +305,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt16()) // XXX determinism? + x := softfloat.Fint32to32(int32(tv.GetInt16())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt16()) // XXX determinism? + x := softfloat.Fint32to64(int32(tv.GetInt16())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -379,11 +380,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt32()) // XXX determinism? + x := softfloat.Fint32to32(tv.GetInt32()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt32()) // XXX determinism? + x := softfloat.Fint32to64(tv.GetInt32()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -456,11 +457,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt64()) // XXX determinism? + x := softfloat.Fint64to32(tv.GetInt64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt64()) // XXX determinism? + x := softfloat.Fint64to64(tv.GetInt64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -533,11 +534,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -602,11 +603,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint8()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint8())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint8()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint8())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -673,11 +674,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint16()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint16())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint16()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint16())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -746,11 +747,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint32()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint32())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint32()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint32())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -825,11 +826,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint64()) // XXX determinism? + x := softfloat.Fuint64to32(tv.GetUint64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint64()) // XXX determinism? + x := softfloat.Fuint64to64(tv.GetUint64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -857,7 +858,7 @@ GNO_CASE: return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt }) - x := int(tv.GetFloat32()) // XXX determinism? + x := int(softfloat.F32toint64(tv.GetFloat32())) tv.T = t tv.SetInt(x) case Int8Kind: @@ -872,7 +873,7 @@ GNO_CASE: return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 }) - x := int8(tv.GetFloat32()) // XXX determinism? + x := int8(softfloat.F32toint32(tv.GetFloat32())) tv.T = t tv.SetInt8(x) case Int16Kind: @@ -887,7 +888,7 @@ GNO_CASE: return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 }) - x := int16(tv.GetFloat32()) // XXX determinism? + x := int16(softfloat.F32toint32(tv.GetFloat32())) tv.T = t tv.SetInt16(x) case Int32Kind: @@ -902,7 +903,7 @@ GNO_CASE: return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 }) - x := int32(tv.GetFloat32()) // XXX determinism? + x := int32(softfloat.F32toint32(tv.GetFloat32())) tv.T = t tv.SetInt32(x) case Int64Kind: @@ -913,7 +914,7 @@ GNO_CASE: return val == trunc }) - x := int64(tv.GetFloat32()) // XXX determinism? + x := int64(softfloat.F32toint64(tv.GetFloat32())) tv.T = t tv.SetInt64(x) case UintKind: @@ -928,7 +929,7 @@ GNO_CASE: return trunc >= 0 && trunc <= math.MaxUint }) - x := uint(tv.GetFloat32()) // XXX determinism? + x := uint(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -943,7 +944,7 @@ GNO_CASE: return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 }) - x := uint8(tv.GetFloat32()) // XXX determinism? + x := uint8(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint8(x) case Uint16Kind: @@ -958,7 +959,7 @@ GNO_CASE: return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 }) - x := uint16(tv.GetFloat32()) // XXX determinism? + x := uint16(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint16(x) case Uint32Kind: @@ -973,7 +974,7 @@ GNO_CASE: return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 }) - x := uint32(tv.GetFloat32()) // XXX determinism? + x := uint32(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint32(x) case Uint64Kind: @@ -988,15 +989,15 @@ GNO_CASE: return trunc >= 0 && trunc <= math.MaxUint }) - x := uint64(tv.GetFloat32()) // XXX determinism? + x := uint64(softfloat.F32touint64(tv.GetFloat32())) 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 := softfloat.F32to64(tv.GetFloat32()) // ??? tv.T = t tv.SetFloat64(x) default: @@ -1009,159 +1010,169 @@ GNO_CASE: case IntKind: validate(Float64Kind, IntKind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt + truncInt64 := softfloat.F64toint64(val) + return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(tv.GetFloat64()) // XXX determinism? + x := int(softfloat.F64toint64(tv.GetFloat64())) tv.T = t tv.SetInt(x) case Int8Kind: validate(Float64Kind, Int8Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 + truncInt64 := softfloat.F64toint64(val) + return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8 }) - x := int8(tv.GetFloat64()) // XXX determinism? + x := int8(softfloat.F64toint32(tv.GetFloat64())) tv.T = t tv.SetInt8(x) case Int16Kind: validate(Float64Kind, Int16Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 + truncInt64 := softfloat.F64toint64(val) + return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16 }) - x := int16(tv.GetFloat64()) // XXX determinism? + x := int16(softfloat.F64toint32(tv.GetFloat64())) tv.T = t tv.SetInt16(x) case Int32Kind: validate(Float64Kind, Int32Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 + truncInt64 := softfloat.F64toint64(val) + return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32 }) - x := int32(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64toint32(tv.GetFloat64()) tv.T = t tv.SetInt32(x) case Int64Kind: validate(Float64Kind, Int64Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - return val == trunc + cmp, _ := softfloat.Fcmp64(val, trunc) + return cmp == 0 }) - x := int64(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64toint64(tv.GetFloat64()) tv.T = t tv.SetInt64(x) case UintKind: validate(Float64Kind, UintKind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } return trunc >= 0 && trunc <= math.MaxUint }) - x := uint(tv.GetFloat64()) // XXX determinism? + x := uint(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint(x) case Uint8Kind: validate(Float64Kind, Uint8Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 + truncInt64 := softfloat.F64toint64(val) + return truncInt64 >= 0 && truncInt64 <= math.MaxUint8 }) - x := uint8(tv.GetFloat64()) // XXX determinism? + x := uint8(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint8(x) case Uint16Kind: validate(Float64Kind, Uint16Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 + truncInt64 := softfloat.F64toint64(val) + return truncInt64 >= 0 && truncInt64 <= math.MaxUint16 }) - x := uint16(tv.GetFloat64()) // XXX determinism? + x := uint16(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint16(x) case Uint32Kind: validate(Float64Kind, Uint32Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 + truncInt64 := softfloat.F64toint64(val) + return truncInt64 >= 0 && truncInt64 <= math.MaxUint32 }) - x := uint32(tv.GetFloat64()) // XXX determinism? + x := uint32(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint32(x) case Uint64Kind: validate(Float64Kind, Uint64Kind, func() bool { val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Trunc(val) - if val != trunc { + if cmp, _ := softfloat.Fcmp64(val, trunc); cmp != 0 { return false } - return trunc >= 0 && trunc <= math.MaxUint64 + truncUint64 := softfloat.F64touint64(val) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint64 }) - x := uint64(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64touint64(tv.GetFloat64()) tv.T = t tv.SetUint64(x) case Float32Kind: validate(Float64Kind, Float32Kind, func() bool { - return tv.GetFloat64() <= math.MaxFloat32 + cmp, _ := softfloat.Fcmp64(tv.GetFloat64(), softfloat.Float64(math.Float64bits(float64(math.MaxFloat32)))) + return cmp <= 0 }) - x := float32(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64to32(tv.GetFloat64()) 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 +1492,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(softfloat.Float32(math.Float32bits(f32))) return // done case Float64Kind: dst.T = t @@ -1495,7 +1506,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(softfloat.Float64(math.Float64bits(f64))) return // done case BigdecKind: dst.T = t @@ -1610,7 +1621,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { dst.T = Float64Type dst.V = nil f, _ := bd.Float64() - dst.SetFloat64(f) + dst.SetFloat64(softfloat.Float64(math.Float64bits(f))) return case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind: fallthrough @@ -1636,7 +1647,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(softfloat.Float32(math.Float32bits(f32))) return case Float64Kind: dst.T = t @@ -1648,7 +1659,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(softfloat.Float64(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 5436347733f..efd26dcf831 100644 --- a/gnovm/pkg/gnolang/values_conversions_test.go +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/stretchr/testify/require" ) @@ -23,5 +24,5 @@ func TestConvertUntypedBigdecToFloat(t *testing.T) { ConvertUntypedBigdecTo(dst, bd, typ) - require.Equal(t, float64(0), dst.GetFloat64()) + require.Equal(t, softfloat.Fintto64(0), dst.GetFloat64()) } diff --git a/gnovm/tests/files/float8.gno b/gnovm/tests/files/float8.gno new file mode 100644 index 00000000000..eafae9fbca5 --- /dev/null +++ b/gnovm/tests/files/float8.gno @@ -0,0 +1,77 @@ +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 = 0x1p127 * (1 + (1 - 0x1p-23)) + 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 +// 1.7976931348623157e+308