6 ms·
Yes, what you presented are WTFs. But the biggest WTF is that in every language, NaN == NaN yields false.
by nayuki 5y ago
Yes, what you presented are WTFs. But the biggest WTF is that in every language, NaN == NaN yields false.
- dTal 5y agoThat makes perfect sense. Why should two NaNs compare equal? NaN is Not A Number - it means "you messed up and did an invalid calculation". You can't do equality comparisons on that.
- mikepurvis 5y agoYeah, and even in the basic case of infinities/limits, it's pretty obvious that they're not equal. Otherwise you could make any equation work by dividing both sides by zero.
- lmm 5y agoBecause one of the basic axioms of equivalence relations (which equality should certainly be) is reflexivity; anything should be equal to itself, and other code will break if it isn't (e.g. in many languages you can corrupt a binary tree structure by inserting multiple NaNs into it, because they'll both compare less than each other). Maybe there are legitimate use cases for that kind of comparison, but you shouldn't use the conventional == operator for it. I think OCaml avoids the problem by having separate operators for floats, and Rust sort-of does by having a distinction between PartialEq and Eq.
- thwarted 5y ago> Because one of the basic axioms of equivalence relations (which equality should certainly be) is reflexivity; anything should be equal to itself, and other code will break if it isn't (e.g. in many languages you can corrupt a binary tree structure by inserting multiple NaNs into it, because they'll both compare less than each other). NaN is not something that can be pointed to. NaN doesn't exist like numbers exist in relation to each other, it's a concept (or at least more conceptual than numbers themselves are conceptual). NaN doesn't exist on a number line, so it makes no sense to order, compare for greater or less than, a NAN with numbers or anything else. It literally means "this doesn't exist". It doesn't have a "self" for reflexivity to apply to. A binary tree's nodes need to be comparable and orderable, and NaN is not comparable to anything. There is no place to put a NaN in a binary tree, nor is there really a use case to "find a NaN quickly" that a binary tree would be useful for (what would the utility be of a binary tree where the keys are all NaN?). NaN is a sentinel, and it exists outside the set of floats, despite being a float itself for type purposes, which is why it needs to be tested for using specific operators (isNaN) and not using equality operators. Part of the utility of NaN is that it can not be tested for using equality. Of course, computers are limited in what can be represented using bits, so NaN does have a specific (set of) binary representation(s), and you could work with those, but that avoids the useful abstraction NaN is mean to represent.
- lmm 5y ago> NaN is a sentinel, and it exists outside the set of floats, despite being a float itself for type purposes, which is why it needs to be tested for using specific operators (isNaN) and not using equality operators. Part of the utility of NaN is that it can not be tested for using equality. That seems like pretty bad type design - if you want a sum type, define an actual sum type. If a given type doesn't have a well-behaved concept of equality or comparison (and to be clear, that means well behaved for any values of that type), it shouldn't support the standard equality or comparison operators. I agree that you probably don't want to put NaN in a binary tree, but the language should support you in not doing that rather than silently breaking when you do.
- kevin_thibedeau 5y agoThe whole point of NaN working this way is that it is not well behaved and raises an alarm when a bad computation is made. Otherwise you run the risk of wiping out a NaN and never knowing that a divide by zero has corrupted your results.
- lmm 5y agoHaving it not compare equal to itself is a pretty crappy way of making it raise an alarm - you're relying on that accidentally causing some kind of visible corruption to your program's datastructures, which is not at all guaranteed. Anyone who cares about not silently getting corrupt results should be turning on floating-point exceptions rather than using NaNs, unless they know exactly what they're doing.
- chrismorgan 5y agoNaN as an approach to handling arithmetic errors only makes sense (weighed against checked arithmetic) if it poisons all computations involving it. I would argue firstly that floating-point numbers should not implement float == float → boolean comparison, because downgrading NaN to a boolean loses its infectious nature, undermining the purpose of NaN as an error-handling (or at least -detection) scheme. But if you’re going to implement float == float → boolean comparison, I would argue that NaN not equalling itself is more reasonable on this axis of poisoning all computations than the alternative, though I will admit that it’s rendered subjective by the fact that there is no inherent virtue in equality (your “normal” path could just as easily be conditional on inequality). I’d really like to see more experimentation into languages that only support checked arithmetic (not just for floating-point numbers, but also for integer types).
- nayuki 5y agoYet null is considered "not an object", but every language other than SQL implements null == null as true. Because x == x is falsified by NaN, it's not obvious to a human auditor or compiler optimization writer than you can't just blindly fold this expression without being very careful about data types. I would sum it up by saying that x == x yielding false violates the principle of least surprise. It'll trip up every beginner who hasn't been explicitly been taught about NaN's behavior.
- mlindner 5y agoNull and nullability in general is also a mistake that many languages carried over from C where I think it originated (though it possibly predated C). So it doesn't surprise me that there are other problems with null, like for example, comparing two nulls.
- ithkuil 5y agoBut this is orthogonal to the equality property: the commkn alternative to using null/nil values is to use algebraic types (e.g. Option<T>), and with them too None == None is true
- wruza 5y agoRemoving nullability of a reference doesn’t mean removing null-relationships, it means properly typing them as Maybe/Option sum type to forcibly diverge code paths that work with non-existence cases. A language without an advanced compile-time type system cannot have these, not to mention untyped languages. You can’t just remove null/none/nil/undef from js, python, etc. NaN self-inequality comes from the old FPU design decision (FCOM/FUCOM instruction group), which is probably questionable (or rational if you dig deeper) in regard of a programmer’s convenience. That carried over to all languages because it’s a fundamental hardware behavior that cannot be fixed without an overhead.
- ithkuil 5y agoNaN doesn't represent a number that doesn't exist, it represents a value that hasn't been computed because the computation yielded an error which wasn't handled yet. Imagine having error values (or exception objects) that get allocated every time an error happens. You don't expect necessary to be able to compare two instances of an exception object, instead you'll call a function that will tell you e.g. which type of error it is. Now, NaN are not really quite like that. For one, it's unusual to stuff the error object in the same place as the actual value and also treating it as if it was the same type as the value. Furthermore reassigning the error object into another variable implicitly "clones" it into a new identity. It's possible to imagine an alternate universe where each arithmetic operation that produces a NaN allocates a new unique number (there are many available bits so that you'll get quite far without a wrap around. But the additional complexity in hw was deeped not worth it.
- zokier 5y agoThat would come directly from IEEE754: > Every NaN shall compare unordered with everything, including itself. (5.11 Details of comparison predicates)
- saagarjha 5y agoIt’s weird, but it exists to let you check for the existence of NaN.