> any programming language where testing if foo = foo can return something mechanically equivalent to false is just plain broken
Isn't this pretty much any language? You're describing how IEEE float NaNs work. I'm not aware of many languages that don't have IEEE-compliant floats.
IEEE floats with non-signalling NaNs make some very aggressive tradeoffs (in the name of performance) that were maybe a reasonable default in 1985 but are certainly not so today. General-purpose programming languages should not be offering them unless very explicitly opted in to.
> Can you actually name a mainstream language that doesn't have NaNs with that behavior? Python, Ruby, Go, Rust, Java, JavaScript, etc all do.
Rust doesn't have that behaviour: Rust floating-point types simply don't have Eq implementations (they have PartialEq only), passing them somewhere where you need an equality-comparable type is a compilation error. OCaml is the only other example I'm aware of (it treats nan as a normal value with a well-behaved place in the order).
> Or maybe what you're saying is really just that they shouldn't have them, and every modern language has gotten this wrong?
Yes. Similar to how modern high-level languages are starting to default to arbitrary-precision integers rather than fixed-size integers (e.g. Python), we should be defaulting to something like Python's decimal or Java's BigDecimal, and have IEEE floating-point as an opt-in case for when you need it.
> Rust floating-point types simply don't have Eq implementations (they have PartialEq only), passing them somewhere where you need an equality-comparable type is a compilation error.
While this is true, you can still say `NaN == NaN` and get false. I claim that that's still the behavior I was describing.
> OCaml is the only other example I'm aware of (it treats nan as a normal value with a well-behaved place in the order).
Again, kind of true - for physical and structural equality it does a bitwise-comparison. However, most people recommend against those equality operators because they are misleading in many cases, and the float-specific equality operator does what I described.
In what ways are bitwise-comparison wrong? For structured data (say, binary-tree backed maps) this is clearly bad because there are potentially multiple representations that we would consider equivalent. But even for floats it is bad - `0 == -0` should be true, but will not be. Further, two different NaNs will be compare as false even though two of the same NaN will compare as true (you're not supposed to be able to distinguish between different representations of NaN).
> While this is true, you can still say `NaN == NaN` and get false. I claim that that's still the behavior I was describing.
Point taken; I do think it would be better for the method on PartialEq to not be called "==". But Rust does offer a standard distinction between well-behaved equality and not, and floats are not considered to implement well-behaved equality, which comes close to what I'm asking for. Like, if JavaScript had `NaN === NaN` then I'd consider that morally a counterexample to what you're talking about, because === is (in a sense) the standard equality operator in JavaScript; in the same way, I'd consider Eq::== to be the standard equality operator in Rust.
> Again, kind of true - for physical and structural equality it does a bitwise-comparison. However, most people recommend against those equality operators because they are misleading in many cases, and the float-specific equality operator does what I described.
I think that's the right way to do it. Generic code using the generic == expects it to have the standard properties - in particular x == x for any x. Code that uses a float-specific operator can be prepared for float-specific behaviour.
> In what ways are bitwise-comparison wrong? For structured data (say, binary-tree backed maps) this is clearly bad because there are potentially multiple representations that we would consider equivalent. But even for floats it is bad - `0 == -0` should be true, but will not be. Further, two different NaNs will be compare as false even though two of the same NaN will compare as true (you're not supposed to be able to distinguish between different representations of NaN).
This is true as far as it goes. Equally, having 0 == -0 is bad (it breaks substitutability, since 1 / 0.0 != 1 / -0.0), and having x != x is super bad. Really there is just no good general-purpose comparison for IEEE 754 floats, and so they should either not implement generic == at all (in languages where that's possible), be avoided by default and hidden away in an "unsafe" area of the standard library, or both.
Isn't this pretty much any language? You're describing how IEEE float NaNs work. I'm not aware of many languages that don't have IEEE-compliant floats.