Floating point NaNs as map keys in Go give you weird results
The last time around I learned that Go 1.21 may have a clear
builtin partly because you can't delete map entries that have a
floating point NaN as their key. Of course
this isn't the only weird thing about NaNs as keys in maps, although
you can pretty much predict all of them from the fact that NaNs
never compare equal to each other.
First, just like you can't delete a map entry that has a key of NaN, you can't retrieve it either, including using the very same NaN that was used to add the entry:
k := math.NaN() m[k] = "Help" v, ok := m[k]
At this point v
is empty and ok
is false; the NaN key wasn't
found in the map although there is an entry with a key of NaN.
Second, you can add more than one entry with a NaN key, in fact you can add the same NaN key to the map repeatedly:
m[k] = "Help" m[k] = "Me" m[k] = "Out" // Now m has at least three entries with NaN // as their key.
You can retrieve the values of these entries only by iterating both the keys and values of the map with a ranged for loop:
for k, v := range m { if math.IsNaN(k) { fmt.Println(k, v) } }
If you iterate only the keys, you run into the first issue; you can't use the keys to retrieve the values from the map. You have to extract the values directly somehow.
I don't think this behavior is strictly required by the Go
specification, because the specification merely talks about things
like 'if the map contains an entry with key x
' (cf). I believe this would
allow Go to have maps treat NaNs as keys specially, instead of using
regular floating point equality on them. Arguably the current
behavior is not in accordance with the specification (or at least
how people may read it); in the cases above it's hard to say that
the map doesn't contain an entry with the key 'k
'.
(Of course the specification could be clarified to say that 'with key
x
' means 'compares equal to the key', which documents this behavior
for NaNs.)
Incidentally, Go's math.NaN()
currently always returns the same
NaN value in terms of bit pattern. We can see this in src/math/bits.go.
Unsurprisingly, Go uses a quiet NaN. Also, Go defers to
the CPU's floating point operations to determine if a floating
point number is a NaN:
// IEEE 754 says that only NaNs satisfy f != f. // [...] return f != f
If you want to manufacture your own NaNs with different bit patterns for whatever reason and then use them for something, see math.Float64frombits() and math.Float64bits() (learning the bit patterns of NaNs is up to you).
|
|