NaN is just an encoding for "undefined operation".
As specified by the standard since its beginning, there are 2 methods for handling undefined operations:
1. Generate a dedicated exception.
2. Return the special value NaN.
The default is to return NaN because this means less work for the programmer, who does not have to write an exception handler, and also because on older CPUs it was expensive to add enough hardware to ensure that exceptions could be handled without slowing down all programs, regardless whether they generated exceptions or not. On modern CPUs with speculative execution this is not really a problem, because they must be able to discard any executed instruction anyway, while running at full speed. Therefore enabling additional reasons for discarding the previously executed instructions, e.g. because of exceptional conditions, just reuses the speculative execution mechanism.
Whoever does not want to handle NaNs must enable the exception for undefined operations and handle that. In that case no NaNs will ever be generated. Enabling this exception may be needed in any case when one sees unexpected NaNs, for debugging the program.
My understanding is that the reasoning behind all this is:
- In 1985 there were a ton of different hardware floating-point implementations with incompatible instructions, making it a nightmare to write floating-point code once that worked on multiple machines
- To address the compatibility problem, IEEE came up with a hardware standard that could do error handling using only CPU registers (no software, since it's a hardware standard)
- With that design constraint, they (reasonably imo) chose to handle errors by making them "poisonous" - once you have a NaN, all operations on it fail, including equality, so the error state propagates rather than potentially accidentally "un-erroring" if you do another operation, leading you into undefined behavior territory
- The standard solved the problem when hardware manufacturers adopted it
- The upstream consequence on software is that if your programming language does anything other than these exact floating-point semantics, the cost is losing hardware acceleration, which makes your floating-point operations way slower
> Shouldn't an operator on incompatible types return undefined? ;)
NaN is a value of the Number type; I think there are some problems with deciding that Number is not compatible with Number for equality.
We just need another value in the boolean type called NaB, and then NaN == NaN can return NaB.
To complement this, also if/then/else should get a new branch called otherwise that is taken when the if clause evaluates to NaB.
This is a matter of choice, not something with an objectively correct answer. Every possible answer has trade offs. I think consistency with the underlying standard defining NaN probably has better tradeoffs in general, and more specific answers can always be built on top of that.
That said, I don’t think undefined in JS has the colloquial meaning you’re using here. The tradeoffs would be potentially much more confusing and error prone for that reason alone.
It might be more “correct” (logically; standard aside) to throw, as others suggest. But that would have considerable ergonomic tradeoffs that might make code implementing simple math incredibly hard to understand in practice.
A language with better error handling ergonomics overall might fare better though.
>A language with better error handling ergonomics overall might fare better though.
So what always trips me up about JavaScript is that if you make a mistake, it silently propagates nonsense through the program. There's no way to configure it to even warn you about it. (There's "use strict", and there should be "use stricter!")
And this aspect of the language is somehow considered sacred, load-bearing infrastructure that may never be altered. (Even though, with "use strict" we already demonstrated that have a mechanism for fixing things without breaking them!)
I think the existence of TS might unfortunately be an unhelpful influence on JS's soundness, because now there's even less pressure to fix it than there was before.
It should return false, right? They are different types of thing, so they can’t be the same thing.
Or, maybe we could say that our variables just represent some ideal things, and if the ideal things they represent are equal, it is reasonable to call the variables equal. 1.0d0, 1.0, 1, and maybe “1” could be equal.
JavaScript has also TypeError which would be more appropriate here. unfortunately undefined has never been used well and it's caused much more pain than it has brought interesting use cases
>Shouldn't an operator on incompatible types return undefined? ;)
Please no, js devs rely too much on boolean collapse for that. Undefined would pass as falsy in many places, causing hard to debug issues.
Besides, conceptually speaking if two things are too different to be compared, doesn’t that tell you that they’re very unequal?
Interesting. So, having a comparison between incomparable types result in false -- what we have now -- is functionally equivalent, in an if-statement, to having the undefined evaluate to false... with the difference that the type coercion is currently one level lower (inside the == operator itself).
It kind of sounds like we need more type coercion because we already have too much type coercion!
I'm not sure what an ergonomic solution would look like though.
Lately I'm more in favour of "makes sense but is a little awkward to read and write" (but becomes effortless once you internalize it because it actually makes sense) over "convenient but not really designed so falls apart once you leave the happy path, and requires you to memorize a long list of exceptions and gotchas."