13 comments

[ 0.22 ms ] story [ 29.6 ms ] thread
NaN == NaN is truly a perversion of equality.

It makes little sense that 1/0 is SIGFPE, but log(-5) is NaN in C.

And the same is true for higher level languages, and their error facilities.

What a mess.

This is also why Rust has separate PartialEq and Eq traits - the latter is only available for types that don't have weird not-self-equal values like floating point NaNs or SQL NULLs. If you lie to Rust and create a wrapper type over f32 or f64 that has Eq, then you'd get unindexable NaN keys that just sit in your hashmap forever.

The real surprise to me is that Python can index NaN keys sometimes, at least by reference to the original NaN. I knew CPython does some Weird Shit with primitive values, so I assume it's because the hashmap is comparing by reference first and then by value.

It would be more satisfying to learn why hash of nan is not guaranteed to be the same. It feels like a bug.
NaN that is not equal to itself _even if it's the same variable_ is not a Python oddity, it's an IEEE 754 oddity.
> we had an unusual discussion about a Python oddity

There are so many discussions about "X language is so weird about it handles numbers!" and it's just IEEE 754 floats.

    >>> my_dict[nan] = 3
    >>> my_dict[nan]
    3
Wait, how does that work if nan isn't equal to itself?
> Last week in the Python Discord we had an unusual discussion about a Python oddity.

Oh, I missed it. But yes, this is more to do with NaN than Python.

> But, of course, you can't actually get to those values by their keys: ... That is, unless you stored the specific instance of nan as a variable:

Worth noting that sets work the same way here, although this was glossed over: you can store multiple NaNs in a set, but not the same NaN instance multiple times. Even though it isn't equal to itself, the insertion process (for both dicts and sets) will consider object identity before object equality:

  >>> x = float('nan')
  >>> {x for _ in range(10)}
  {nan}
And, yes, the same is of course true of `collections.Counter`.
IEEE 754 prescripes, for better or worse, that any mathematical comparison operator (==, <, > ….) involving at least one NaN must always return false, including comparison against itself. This is annoying for something like dictionaries or hashtables. C# has a solution: if you call a.Equals(b) on two floats a and b, it will return true also if both are NaN. I think this is a cool solution: it keeps the meaning of math operators the same identical with other languages, but you still have sensible behavior for containers. I believe this behavior is copied from Java.
This got me curious, and yeah it turns out Elm's dictionary implementation uses values, not pointers when retrieving values.

   elm repl
  ---- Elm 0.19.1 ----------------------------------------------------------------
  Say :help for help and :exit to exit! More at <https://elm-lang.org/0.19.1/repl>
  --------------------------------------------------------------------------------
  > import Dict exposing (Dict)
  > nan = 0/0
  NaN : Float
  > nan
  NaN : Float
  > nan == nan
  False : Bool
  > naan = 0/0
  NaN : Float
  > d = Dict.fromList [(nan, "a"), (naan, "b")]
  Dict.fromList [(NaN,"a"),(NaN,"b")]
      : Dict Float String
  > Dict.toList d
  [(NaN,"a"),(NaN,"b")]
      : List ( Float, String )
  > Dict.keys d
  [NaN,NaN] : List Float
  > Dict.get nan d
  Nothing : Maybe String
  > Dict.get naan d
  Nothing : Maybe String