From 663edac6246046f6cac0af912b0c38722f5eaa66 Mon Sep 17 00:00:00 2001
From: 6h057 <15034695+omarsy@users.noreply.github.com>
Date: Mon, 13 Jan 2025 12:11:33 +0100
Subject: [PATCH] feat(gnovm): add software floating point package (#3185)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes: #312
The idea to use softfloat and work originates from this PR:
https://github.com/gnolang/gno/pull/2863
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
---------
Co-authored-by: Morgan Bazalgette
Co-authored-by: Miloš Živković
---
gno.land/pkg/sdk/vm/convert.go | 5 +-
gnovm/pkg/gnolang/frame.go | 5 +-
gnovm/pkg/gnolang/gonative.go | 19 +-
gnovm/pkg/gnolang/internal/softfloat/copy.sh | 32 +
.../internal/softfloat/runtime_softfloat64.go | 631 ++++++++++++++++++
.../softfloat/runtime_softfloat64_test.go | 204 ++++++
.../gnolang/internal/softfloat/softfloat.go | 134 ++++
gnovm/pkg/gnolang/op_binary.go | 46 +-
gnovm/pkg/gnolang/op_inc_dec.go | 9 +-
gnovm/pkg/gnolang/op_unary.go | 5 +-
gnovm/pkg/gnolang/values.go | 21 +-
gnovm/pkg/gnolang/values_conversions.go | 258 +++----
gnovm/pkg/gnolang/values_conversions_test.go | 3 +-
gnovm/pkg/gnolang/values_string.go | 9 +-
gnovm/tests/files/float8.gno | 166 +++++
15 files changed, 1360 insertions(+), 187 deletions(-)
create mode 100644 gnovm/pkg/gnolang/internal/softfloat/copy.sh
create mode 100644 gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go
create mode 100644 gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go
create mode 100644 gnovm/pkg/gnolang/internal/softfloat/softfloat.go
create mode 100644 gnovm/tests/files/float8.gno
diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go
index cafb6cad67f..dbaabcfbc4b 100644
--- a/gno.land/pkg/sdk/vm/convert.go
+++ b/gno.land/pkg/sdk/vm/convert.go
@@ -3,6 +3,7 @@ package vm
import (
"encoding/base64"
"fmt"
+ "math"
"strconv"
"strings"
@@ -143,11 +144,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(math.Float32bits(float32(value)))
return
case gno.Float64Type:
value := convertFloat(arg, 64)
- tv.SetFloat64(value)
+ tv.SetFloat64(math.Float64bits(value))
return
default:
panic(fmt.Sprintf("unexpected primitive type %s", bt.String()))
diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go
index 2ac1027eb32..60f19979b7a 100644
--- a/gnovm/pkg/gnolang/frame.go
+++ b/gnovm/pkg/gnolang/frame.go
@@ -2,6 +2,7 @@ package gnolang
import (
"fmt"
+ "math"
"strings"
)
@@ -207,9 +208,9 @@ func toConstExpTrace(cte *ConstExpr) string {
case Uint64Type:
return fmt.Sprintf("%d", tv.GetUint64())
case Float32Type:
- return fmt.Sprintf("%v", tv.GetFloat32())
+ return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32()))
case Float64Type:
- return fmt.Sprintf("%v", tv.GetFloat64())
+ return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64()))
}
}
diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go
index 5a39c76b5e1..85fc8b70051 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
@@ -329,9 +332,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.F64to32(math.Float64bits(rv.Float())))
case reflect.Float64:
- tv.SetFloat64(rv.Float())
+ tv.SetFloat64(math.Float64bits(rv.Float()))
case reflect.Array:
tv.V = alloc.NewNative(rv)
case reflect.Slice:
@@ -428,11 +431,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.F64to32(math.Float64bits(rv.Float())))
}
case Float64Kind:
if lvl != 0 {
- tv.SetFloat64(rv.Float())
+ tv.SetFloat64(math.Float64bits(rv.Float()))
}
case BigintKind:
panic("not yet implemented")
@@ -644,9 +647,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.F64to32(math.Float64bits(rv.Float())))
case reflect.Float64:
- tv.SetFloat64(rv.Float())
+ tv.SetFloat64(math.Float64bits(rv.Float()))
case reflect.Array:
rvl := rv.Len()
if rv.Type().Elem().Kind() == reflect.Uint8 {
@@ -1049,9 +1052,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(softfloat.F32to64(tv.GetFloat32())))
case Float64Type:
- rv.SetFloat(tv.GetFloat64())
+ rv.SetFloat(math.Float64frombits(tv.GetFloat64()))
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..30f66dff620
--- /dev/null
+++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go
@@ -0,0 +1,134 @@
+// 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
+
+const (
+ mask = 0x7FF
+ shift = 64 - 11 - 1
+ bias = 1023
+)
+
+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) }
+
+// Trunc
+
+func Ftrunc64(f uint64) uint64 { return trunc(f) }
+func Ftrunc32(f uint32) uint32 { return f64to32(trunc(f32to64(f))) }
+
+func trunc(x uint64) uint64 {
+ cmp, _ := Fcmp64(x, Fintto64(0))
+ if _, _, _, isInf, IsNaN := Funpack64(x); cmp == 0 || isInf || IsNaN {
+ return x
+ }
+
+ d, _ := modf(x)
+ return d
+}
+
+func modf(u uint64) (it uint64, frac uint64) {
+ if Flt64(u, fint64to64(1)) {
+ switch {
+ case Flt64(u, fint64to64(0)):
+ it, frac = modf(Fneg64(u))
+ return -it, -frac
+ case feq64(u, fint64to64(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 = fsub64(u, it)
+ return
+}
diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go
index 0f66da5e685..765f3ccbfbd 100644
--- a/gnovm/pkg/gnolang/op_binary.go
+++ b/gnovm/pkg/gnolang/op_binary.go
@@ -6,6 +6,7 @@ import (
"math/big"
"github.com/cockroachdb/apd/v3"
+ "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"
"github.com/gnolang/gno/tm2/pkg/overflow"
)
@@ -394,9 +395,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 softfloat.Feq32(lv.GetFloat32(), rv.GetFloat32())
case Float64Kind:
- return (lv.GetFloat64() == rv.GetFloat64()) // XXX determinism?
+ return softfloat.Feq64(lv.GetFloat64(), rv.GetFloat64())
case BigintKind:
lb := lv.V.(BigintValue).V
rb := rv.V.(BigintValue).V
@@ -535,9 +536,9 @@ func isLss(lv, rv *TypedValue) bool {
case Uint64Kind:
return (lv.GetUint64() < rv.GetUint64())
case Float32Kind:
- return (lv.GetFloat32() < rv.GetFloat32()) // XXX determinism?
+ return softfloat.Flt32(lv.GetFloat32(), rv.GetFloat32())
case Float64Kind:
- return (lv.GetFloat64() < rv.GetFloat64()) // XXX determinism?
+ return softfloat.Flt64(lv.GetFloat64(), rv.GetFloat64())
case BigintKind:
lb := lv.V.(BigintValue).V
rb := rv.V.(BigintValue).V
@@ -579,9 +580,9 @@ func isLeq(lv, rv *TypedValue) bool {
case Uint64Kind:
return (lv.GetUint64() <= rv.GetUint64())
case Float32Kind:
- return (lv.GetFloat32() <= rv.GetFloat32()) // XXX determinism?
+ return softfloat.Fle32(lv.GetFloat32(), rv.GetFloat32())
case Float64Kind:
- return (lv.GetFloat64() <= rv.GetFloat64()) // XXX determinism?
+ return softfloat.Fle64(lv.GetFloat64(), rv.GetFloat64())
case BigintKind:
lb := lv.V.(BigintValue).V
rb := rv.V.(BigintValue).V
@@ -623,9 +624,9 @@ func isGtr(lv, rv *TypedValue) bool {
case Uint64Kind:
return (lv.GetUint64() > rv.GetUint64())
case Float32Kind:
- return (lv.GetFloat32() > rv.GetFloat32()) // XXX determinism?
+ return softfloat.Fgt32(lv.GetFloat32(), rv.GetFloat32())
case Float64Kind:
- return (lv.GetFloat64() > rv.GetFloat64()) // XXX determinism?
+ return softfloat.Fgt64(lv.GetFloat64(), rv.GetFloat64())
case BigintKind:
lb := lv.V.(BigintValue).V
rb := rv.V.(BigintValue).V
@@ -667,9 +668,9 @@ func isGeq(lv, rv *TypedValue) bool {
case Uint64Kind:
return (lv.GetUint64() >= rv.GetUint64())
case Float32Kind:
- return (lv.GetFloat32() >= rv.GetFloat32()) // XXX determinism?
+ return softfloat.Fge32(lv.GetFloat32(), rv.GetFloat32())
case Float64Kind:
- return (lv.GetFloat64() >= rv.GetFloat64()) // XXX determinism?
+ return softfloat.Fge64(lv.GetFloat64(), rv.GetFloat64())
case BigintKind:
lb := lv.V.(BigintValue).V
rb := rv.V.(BigintValue).V
@@ -732,10 +733,10 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception {
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())
@@ -807,10 +808,10 @@ func subAssign(lv, rv *TypedValue) *Exception {
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())
@@ -879,10 +880,10 @@ func mulAssign(lv, rv *TypedValue) *Exception {
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())
@@ -975,20 +976,17 @@ func quoAssign(lv, rv *TypedValue) *Exception {
// XXX Handling float overflows is more complex.
case Float32Type:
// NOTE: gno doesn't fuse *+.
- y := rv.GetFloat32()
- ok = y != 0
+ ok = !softfloat.Feq32(rv.GetFloat32(), softfloat.Fintto32(0))
+
if ok {
- lv.SetFloat32(lv.GetFloat32() / y)
+ lv.SetFloat32(softfloat.Fdiv32(lv.GetFloat32(), rv.GetFloat32()))
}
- // XXX FOR DETERMINISM, PANIC IF NAN.
case Float64Type:
// NOTE: gno doesn't fuse *+.
- y := rv.GetFloat64()
- ok = y != 0
+ ok = !softfloat.Feq64(rv.GetFloat64(), softfloat.Fintto64(0))
if ok {
- lv.SetFloat64(lv.GetFloat64() / y)
+ lv.SetFloat64(softfloat.Fdiv64(lv.GetFloat64(), rv.GetFloat64()))
}
- // XXX FOR DETERMINISM, PANIC IF NAN.
case BigintType, UntypedBigintType:
if rv.GetBigInt().Sign() == 0 {
ok = false
diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go
index 1e68e195596..c67a4be6ed5 100644
--- a/gnovm/pkg/gnolang/op_inc_dec.go
+++ b/gnovm/pkg/gnolang/op_inc_dec.go
@@ -5,6 +5,7 @@ import (
"math/big"
"github.com/cockroachdb/apd/v3"
+ "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"
"github.com/gnolang/gno/tm2/pkg/overflow"
)
@@ -57,9 +58,9 @@ func (m *Machine) doOpInc() {
case Uint64Type:
lv.SetUint64(lv.GetUint64() + 1)
case Float32Type:
- lv.SetFloat32(lv.GetFloat32() + 1)
+ lv.SetFloat32(softfloat.Fadd32(lv.GetFloat32(), softfloat.Fintto32(1)))
case Float64Type:
- lv.SetFloat64(lv.GetFloat64() + 1)
+ lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), softfloat.Fintto64(1)))
case BigintType, UntypedBigintType:
lb := lv.GetBigInt()
lb = big.NewInt(0).Add(lb, big.NewInt(1))
@@ -129,9 +130,9 @@ func (m *Machine) doOpDec() {
case Uint64Type:
lv.SetUint64(lv.GetUint64() - 1)
case Float32Type:
- lv.SetFloat32(lv.GetFloat32() - 1)
+ lv.SetFloat32(softfloat.Fsub32(lv.GetFloat32(), softfloat.Fintto32(1)))
case Float64Type:
- lv.SetFloat64(lv.GetFloat64() - 1)
+ lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), softfloat.Fintto64(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..469c80b8dac 100644
--- a/gnovm/pkg/gnolang/op_unary.go
+++ b/gnovm/pkg/gnolang/op_unary.go
@@ -5,6 +5,7 @@ import (
"math/big"
"github.com/cockroachdb/apd/v3"
+ "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"
)
func (m *Machine) doOpUpos() {
@@ -46,9 +47,9 @@ func (m *Machine) doOpUneg() {
case Uint64Type:
xv.SetUint64(-xv.GetUint64())
case Float32Type:
- xv.SetFloat32(-xv.GetFloat32())
+ xv.SetFloat32(softfloat.Fneg32(xv.GetFloat32()))
case Float64Type:
- xv.SetFloat64(-xv.GetFloat64())
+ xv.SetFloat64(softfloat.Fneg64(xv.GetFloat64()))
case UntypedBigintType, BigintType:
bv := xv.V.(BigintValue)
xv.V = BigintValue{V: new(big.Int).Neg(bv.V)}
diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go
index 4c2e2835f95..da887764c8e 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,13 +1120,13 @@ 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)
return data
case Float64Type:
data = make([]byte, 8)
- u64 := math.Float64bits(tv.GetFloat64())
+ u64 := tv.GetFloat64()
binary.LittleEndian.PutUint64(
data, u64)
return data
@@ -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 uint32) {
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
+ *(*uint32)(unsafe.Pointer(&tv.N)) = n
}
-func (tv *TypedValue) GetFloat32() float32 {
+func (tv *TypedValue) GetFloat32() uint32 {
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 *(*uint32)(unsafe.Pointer(&tv.N))
}
-func (tv *TypedValue) SetFloat64(n float64) {
+func (tv *TypedValue) SetFloat64(n uint64) {
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
+ *(*uint64)(unsafe.Pointer(&tv.N)) = n
}
-func (tv *TypedValue) GetFloat64() float64 {
+func (tv *TypedValue) GetFloat64() uint64 {
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 *(*uint64)(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..e1c9378fe67 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:
@@ -847,156 +848,155 @@ GNO_CASE:
switch k {
case IntKind:
validate(Float32Kind, IntKind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt
+ truncInt64 := softfloat.F32toint64(trunc)
+ return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt
})
- x := int(tv.GetFloat32()) // XXX determinism?
+ x := int(softfloat.F32toint64(tv.GetFloat32()))
tv.T = t
tv.SetInt(x)
case Int8Kind:
validate(Float32Kind, Int8Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8
+ truncInt64 := softfloat.F32toint64(trunc)
+ return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8
})
- x := int8(tv.GetFloat32()) // XXX determinism?
+ x := int8(softfloat.F32toint32(tv.GetFloat32()))
tv.T = t
tv.SetInt8(x)
case Int16Kind:
validate(Float32Kind, Int16Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16
+ truncInt64 := softfloat.F32toint64(trunc)
+ return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16
})
- x := int16(tv.GetFloat32()) // XXX determinism?
+ x := int16(softfloat.F32toint32(tv.GetFloat32()))
tv.T = t
tv.SetInt16(x)
case Int32Kind:
validate(Float32Kind, Int32Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32
+ truncInt64 := softfloat.F32toint64(trunc)
+ return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32
})
- x := int32(tv.GetFloat32()) // XXX determinism?
+ x := softfloat.F32toint32(tv.GetFloat32())
tv.T = t
tv.SetInt32(x)
case Int64Kind:
validate(Float32Kind, Int64Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- return val == trunc
+ return softfloat.Feq32(trunc, tv.GetFloat32())
})
- x := int64(tv.GetFloat32()) // XXX determinism?
+ x := softfloat.F32toint64(tv.GetFloat32())
tv.T = t
tv.SetInt64(x)
case UintKind:
validate(Float32Kind, UintKind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return trunc >= 0 && trunc <= math.MaxUint
+ truncUint64 := softfloat.F32touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= math.MaxUint
})
- x := uint(tv.GetFloat32()) // XXX determinism?
+ x := uint(softfloat.F32touint64(tv.GetFloat32()))
tv.T = t
tv.SetUint(x)
case Uint8Kind:
validate(Float32Kind, Uint8Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8
+ truncUint64 := softfloat.F32touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= math.MaxUint8
})
- x := uint8(tv.GetFloat32()) // XXX determinism?
+ x := uint8(softfloat.F32touint64(tv.GetFloat32()))
tv.T = t
tv.SetUint8(x)
case Uint16Kind:
validate(Float32Kind, Uint16Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16
+ truncUint64 := softfloat.F32touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= math.MaxUint16
})
- x := uint16(tv.GetFloat32()) // XXX determinism?
+ x := uint16(softfloat.F32touint64(tv.GetFloat32()))
tv.T = t
tv.SetUint16(x)
case Uint32Kind:
validate(Float32Kind, Uint32Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32
+ truncUint64 := softfloat.F32touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= math.MaxUint32
})
- x := uint32(tv.GetFloat32()) // XXX determinism?
+ x := uint32(softfloat.F32touint64(tv.GetFloat32()))
tv.T = t
tv.SetUint32(x)
case Uint64Kind:
validate(Float32Kind, Uint64Kind, func() bool {
- val := float64(tv.GetFloat32())
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc32(tv.GetFloat32())
- if val != trunc {
+ if !softfloat.Feq32(trunc, tv.GetFloat32()) {
return false
}
- return trunc >= 0 && trunc <= math.MaxUint
+ truncUint64 := softfloat.F32touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= math.MaxUint
})
- x := uint64(tv.GetFloat32()) // XXX determinism?
+ x := 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:
@@ -1008,160 +1008,160 @@ GNO_CASE:
switch k {
case IntKind:
validate(Float64Kind, IntKind, func() bool {
- val := tv.GetFloat64()
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt
+ truncInt64 := softfloat.F64toint64(trunc)
+ return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt
})
- x := int(tv.GetFloat64()) // XXX determinism?
+ xp, _ := softfloat.F64toint(tv.GetFloat64())
+ x := int(xp)
tv.T = t
tv.SetInt(x)
case Int8Kind:
validate(Float64Kind, Int8Kind, func() bool {
- val := tv.GetFloat64()
- trunc := math.Trunc(val)
+ trunc := softfloat.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8
+ truncInt64 := softfloat.F64toint64(trunc)
+ 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.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16
+ truncInt64 := softfloat.F64toint64(trunc)
+ 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.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32
+ truncInt64 := softfloat.F64toint64(trunc)
+ 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.Ftrunc64(tv.GetFloat64())
- return val == trunc
+ return softfloat.Feq64(trunc, tv.GetFloat64())
})
- 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.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return trunc >= 0 && trunc <= math.MaxUint
+ truncUint64 := softfloat.F64touint64(trunc)
+
+ return truncUint64 >= 0 && truncUint64 <= 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.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8
+ truncUint64 := softfloat.F64touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= 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.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16
+ truncUint64 := softfloat.F64touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= 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)
-
- if val != trunc {
+ trunc := softfloat.Ftrunc64(tv.GetFloat64())
+ if !softfloat.Feq64(trunc, tv.GetFloat64()) {
return false
}
- return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32
+ truncUint64 := softfloat.F64touint64(trunc)
+ return truncUint64 >= 0 && truncUint64 <= 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.Ftrunc64(tv.GetFloat64())
- if val != trunc {
+ if !softfloat.Feq64(tv.GetFloat64(), trunc) {
return false
}
- return trunc >= 0 && trunc <= math.MaxUint64
+ truncUint64 := softfloat.F64touint64(trunc)
+ 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
+ return softfloat.Fle64(tv.GetFloat64(), math.Float64bits(float64(math.MaxFloat32)))
})
- 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 +1481,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(math.Float32bits(f32))
return // done
case Float64Kind:
dst.T = t
@@ -1495,7 +1495,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(math.Float64bits(f64))
return // done
case BigdecKind:
dst.T = t
@@ -1610,7 +1610,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) {
dst.T = Float64Type
dst.V = nil
f, _ := bd.Float64()
- dst.SetFloat64(f)
+ dst.SetFloat64(math.Float64bits(f))
return
case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind:
fallthrough
@@ -1636,7 +1636,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(math.Float32bits(f32))
return
case Float64Kind:
dst.T = t
@@ -1648,7 +1648,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(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..5538e973bdc 100644
--- a/gnovm/pkg/gnolang/values_conversions_test.go
+++ b/gnovm/pkg/gnolang/values_conversions_test.go
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/cockroachdb/apd/v3"
+ "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"
"github.com/stretchr/testify/require"
)
@@ -24,7 +25,7 @@ func TestConvertUntypedBigdecToFloat(t *testing.T) {
ConvertUntypedBigdecTo(dst, bd, typ)
- require.Equal(t, float64(0), dst.GetFloat64())
+ require.True(t, softfloat.Feq64(dst.GetFloat64(), 0))
}
func TestBitShiftingOverflow(t *testing.T) {
diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go
index a414f440e4e..fdf0c8f55de 100644
--- a/gnovm/pkg/gnolang/values_string.go
+++ b/gnovm/pkg/gnolang/values_string.go
@@ -2,6 +2,7 @@ package gnolang
import (
"fmt"
+ "math"
"reflect"
"strconv"
"strings"
@@ -339,9 +340,9 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo
case Uint64Type:
return fmt.Sprintf("%d", tv.GetUint64())
case Float32Type:
- return fmt.Sprintf("%v", tv.GetFloat32())
+ return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32()))
case Float64Type:
- return fmt.Sprintf("%v", tv.GetFloat64())
+ return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64()))
case UntypedBigintType, BigintType:
return tv.V.(BigintValue).V.String()
case UntypedBigdecType, BigdecType:
@@ -447,9 +448,9 @@ func (tv TypedValue) ProtectedString(seen *seenValues) string {
case Uint64Type:
vs = fmt.Sprintf("%d", tv.GetUint64())
case Float32Type:
- vs = fmt.Sprintf("%v", tv.GetFloat32())
+ vs = fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32()))
case Float64Type:
- vs = fmt.Sprintf("%v", tv.GetFloat64())
+ vs = fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64()))
// Complex types that require recusion protection.
default:
vs = nilStr
diff --git a/gnovm/tests/files/float8.gno b/gnovm/tests/files/float8.gno
new file mode 100644
index 00000000000..989d1b4fd61
--- /dev/null
+++ b/gnovm/tests/files/float8.gno
@@ -0,0 +1,166 @@
+package main
+
+import "math"
+
+func main() {
+ asVars()
+ asConsts()
+}
+
+func asVars() {
+ 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 f32Max float32 = math.MaxFloat32
+ var f64Max float64 = math.MaxFloat64
+ var f64Min float64 = math.SmallestNonzeroFloat64
+ var f32Min float32 = math.SmallestNonzeroFloat32
+ println(f32Max / 2)
+ println(f64Max / 2)
+ println((f32Max - 1) + 1)
+ println((f64Max - 1) + 1)
+ println((f32Max / 2) * 2)
+ println((f64Max / 2) * 2)
+ println(f32Max - 1)
+ println(f64Max - 1)
+ println(f64Min / 2)
+ println(f32Min / 2)
+ 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(f32Max))
+ println(float64(f32Max))
+ println(float64(f64Max))
+}
+
+func asConsts() {
+ const i8 int8 = 127
+ const i16 int16 = 32767
+ const i32 int32 = 2147483647
+ const i64 int64 = 9223372036854775807
+ const i int = 9223372036854775807
+ const u8 uint8 = 255
+ const u16 uint16 = 65535
+ const u32 uint32 = 4294967295
+ const u64 uint64 = 18446744073709551615
+ const f32Max float32 = math.MaxFloat32
+ const f64Max float64 = math.MaxFloat64
+ const f64Min float64 = math.SmallestNonzeroFloat64
+ const f32Min float32 = math.SmallestNonzeroFloat32
+ println(f32Max / 2)
+ println(f64Max / 2)
+ println((f32Max - 1) + 1)
+ println((f64Max - 1) + 1)
+ println((f32Max / 2) * 2)
+ println((f64Max / 2) * 2)
+ println(f32Max - 1)
+ println(f64Max - 1)
+ println(f64Min / 2)
+ println(f32Min / 2)
+ 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(f32Max))
+ println(float64(f32Max))
+ println(float64(f64Max))
+}
+
+// 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
+// 0
+// 0
+// 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
+// 1.7976931348623157e+308
+// 1.7014117e+38
+// 8.988465674311579e+307
+// 3.4028235e+38
+// 1.7976931348623157e+308
+// 3.4028235e+38
+// 1.7976931348623157e+308
+// 3.4028235e+38
+// 1.7976931348623157e+308
+// 0
+// 0
+// 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
+// 1.7976931348623157e+308