Skip to content

Commit c9a4b34

Browse files
committed
fix NaN for float32. Ensure that +0.0 = -0.0
1 parent f0f5f42 commit c9a4b34

File tree

2 files changed

+44
-16
lines changed

2 files changed

+44
-16
lines changed

gnovm/pkg/gnolang/values.go

+29-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gnolang
33
import (
44
"encoding/binary"
55
"fmt"
6+
"math"
67
"math/big"
78
"math/rand/v2"
89
"reflect"
@@ -1459,21 +1460,16 @@ func (tv *TypedValue) AssertNonNegative(msg string) {
14591460
}
14601461
}
14611462

1462-
// uvnan is the unsigned value of NaN. It can be obtained by applying the following:
1463-
// { var nan = math.NaN(); var uvnan = *(*uint64)(unsafe.Pointer(&nan)) }
1464-
const uvnan = 0x7FF8000000000001
1465-
1466-
// randNaN returns an uint64 representation of NaN with a random payload of 53 bits.
1467-
func randNaN() uint64 {
1468-
return rand.Uint64()&^uvnan | uvnan //nolint:gosec
1463+
// randNaN64 returns an uint64 representation of NaN with a random payload.
1464+
func randNaN64() uint64 {
1465+
const uvnan64 = 0x7FF8000000000001 // math.Float64bits(math.NaN())
1466+
return rand.Uint64()&^uvnan64 | uvnan64 //nolint:gosec
14691467
}
14701468

1471-
// IsNaN reports wether tv is an IEEE 754 "not a number" value.
1472-
func (tv *TypedValue) IsNaN() bool {
1473-
if tv.HasKind(Float64Kind) {
1474-
return uvnan == tv.GetFloat64()&uvnan
1475-
}
1476-
return false
1469+
// randNaN32 returns an uint32 representation of NaN with a random payload.
1470+
func randNaN32() uint32 {
1471+
const uvnan32 = 0x7FC00000 // math.Float32bits(float32(math.NaN()))
1472+
return rand.Uint32()&^uvnan32 | uvnan32 //nolint:gosec
14771473
}
14781474

14791475
func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey {
@@ -1494,9 +1490,26 @@ func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey {
14941490
}
14951491
switch bt := baseOf(tv.T).(type) {
14961492
case PrimitiveType:
1497-
if tv.IsNaN() {
1498-
// if NaN, generate a random payload to ensure key uniqueness.
1499-
tv.SetFloat64(randNaN())
1493+
if tv.HasKind(Float64Kind) {
1494+
f := math.Float64frombits(tv.GetFloat64())
1495+
if f != f {
1496+
// If NaN, shuffle its value, thus allowing several NaN per map
1497+
// and ensuring that a value cannot be retrieved by NaN.
1498+
tv.SetFloat64(randNaN64())
1499+
} else if f == 0.0 {
1500+
// If zero, clear the sign, so +0.0 and -0.0 match the same key.
1501+
tv.SetFloat64(math.Float64bits(+0.0))
1502+
}
1503+
} else if tv.HasKind(Float32Kind) {
1504+
f := math.Float32frombits(tv.GetFloat32())
1505+
if f != f {
1506+
// If NaN, shuffle its value, thus allowing several NaN per map
1507+
// and ensuring that a value cannot be retrieved by NaN.
1508+
tv.SetFloat32(randNaN32())
1509+
} else if f == 0.0 {
1510+
// If zero, clear the sign, so +0.0 and -0.0 match the same key.
1511+
tv.SetFloat32(math.Float32bits(+0.0))
1512+
}
15001513
}
15011514
pbz := tv.PrimitiveBytes()
15021515
bz = append(bz, pbz...)

gnovm/tests/files/map31.gno

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"math"
6+
)
7+
8+
var NaN = float32(math.NaN())
9+
10+
func main() {
11+
fmt.Println(map[float32]int{NaN: 1, NaN: 2})
12+
}
13+
14+
// Output:
15+
// map[NaN:1 NaN:2]

0 commit comments

Comments
 (0)