gno run and go run disagree on float64 + and - for certain operands. The divergence appears when the result underflows to a subnormal.
I found it by an arithmetic identity synthesized by my tool. I haven't checked deployed contracts for this pattern.
MRE
package main
import "math"
func main() {
a := math.Float64frombits(9333378022939403091) // -2.662107816930723e-301
b := math.Float64frombits(110005986185704326) // 2.662107858822336e-301
println(math.Float64bits(a + b))
}
$ go run main.go
847895691526144
$ gno run main.go
202154086
847895691526144 is correct (4.189161324398747e-309); gno returns 202154086 (9.98e-316).
Subtraction is affected too:
package main
import "math"
func main() {
p := math.Float64frombits(110005986185704326)
q := math.Float64frombits(110005986084627283)
println(math.Float64bits(p - q))
}
$ go run main.go
847895691526144
$ gno run main.go
202154086
Notes
- Triggers when two near-equal, opposite-sign normal
float64 cancel into a subnormal result (|result| < 2.2e-308).
* and / are not affected; direct subnormal operands are not affected.
- Output uses
math.Float64bits because builtin println formats floats differently in gno and go; the underlying values differ regardless of formatting.
Environment
- gno: 991200c
- go:
go1.24.4 linux/amd64
gno runandgo rundisagree onfloat64+and-for certain operands. The divergence appears when the result underflows to a subnormal.I found it by an arithmetic identity synthesized by my tool. I haven't checked deployed contracts for this pattern.
MRE
847895691526144is correct (4.189161324398747e-309);gnoreturns202154086(9.98e-316).Subtraction is affected too:
Notes
float64cancel into a subnormal result (|result| < 2.2e-308).*and/are not affected; direct subnormal operands are not affected.math.Float64bitsbecause builtinprintlnformats floats differently in gno and go; the underlying values differ regardless of formatting.Environment
go1.24.4 linux/amd64