Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • Also, NaN is the only value in JS that isn't === to itself, so if for some reason you want to test for strict value identity with the value NaN of type number, that's one way to do it:

    if(x !== x) ... // x is NaN

  • tl;dr:

    - NaN is a floating point number, and NaN != NaN by definition in the IEEE 754-2019 floating point number standard, regardless of the programming language, there's nothing JavaScript-specific here.

    - In JS Number.isNaN(v) returns true for NaN and anything that's not a number. And in JS, s * n and n * s return NaN for any non empty string s and any number n ("" * n returns 0). (EDIT: WRONG, sée below)

  • I always thought of NaN as more of the concept of not-a-number the way that infinity in math is not a specific value but the concept of some unbounded largest possible value.

    Therefore, trying to do math with either (for example: NaN/NaN or inf./inf.) was to try to pin them down to something tangible and no longer conceptual — therefore disallowed.

  • > And in JS, s * n and n * s return NaN for any non empty string s and any number n ("" * n returns 0).

    No? It is easy to verify that `"3" * 4` evaluates to 12. The full answer is that * converts its operands into primitives (with a hint of being number), and any string that can be parsed as a number converts to that number. Otherwise it converts to NaN.

  • console.log(new Array(16).join("wat"-1) + " Batman!")
  • Indeed, this shtick was funnier 13 years ago. https://www.destroyallsoftware.com/talks/wat

    JavaScript is a quirky, badly-designed language and I think that is common knowledge at this point.

  • Opened Web Inspector in Safari and pasted the above. (I knew what to expect but did not know how it would work … me trying to figure out what subtracting 1 from a string (ASCII?) would give you. But very related to this post.)
  • Equality is a very slippery mathematical relationship. This observation formed the genesis of modern Category Theory [0].

    NaN is an error monad.

    [0] https://www.ams.org/journals/tran/1945-058-00/S0002-9947-194...

  • In the error monad NaN = NaN (or Nothing = Nothing or None = None, depending on your terminology) because mathematical equality is an equivalence relation. There are many foundational debates about equality, but whether or not it is an equivalence is never the question.

    The root of the problem, completely overlooked by OP is that IEEE 754 comparison is not an equivalence relation. It's a partial equivalence relation (PER). It does have its utility, but these things can be weird and they are definitely not interchangeable with actual equivalence relations. Actual, sane, comparison of floating points got standardized eventually, but probably too late https://en.wikipedia.org/wiki/IEEE_754#Total-ordering_predic.... It's actually kinda nuts that the partial relation is the one that you get by default (no, your sorting function on float arrays does not sort it).

  • I get the idea behind NaN != NaN, but has there ever been any instance where this design decision has made practical code better instead of becoming a tripping hazard and requiring extensive special casing?

    I'm also not a fan of the other property that NaN evaluates to false for all three of <, > and =, even though I don't have a good idea what to do otherwise.

    I think as programmers, we usually assume that "not (a > b)" implies "a <= b" and vice-versa and often rely on that assumption implicitly. NaN breaks that assumption, which could lead to unexpected behavior.

    Consider something like this (in JS) :

      function make_examples(num_examples) {
        if (num_examples <= 0) {
          throw Error("num_examples must be 1 or more");
        }
        const examples = [];
        for (let i = 0; i < num_examples; i++) {
          examples.push(make_example(i));
        }
        // we assume that num_examples >= 1 here, so the loop ran at least once and the array cannot be empty.
        postprocess_first_example(examples[0]); // <-- (!)
        return examples;
      }
    
    If somehow num_examples were NaN, the (!) line would fail unexpectedly because the array would be empty.
    by xg15
  • I don't understand what your example has to do with the NaN != NaN case. You're not comparing NaN to NaN anywhere in it.
  • NaN comes from parsing results or Infinity occurring in operations. I personally ends up more to use Number.isFinite(), which will be false on both occurrences when I need a real (haha) numeric answer.
  • Also remember that NaN is represented in multiple ways bitwise:

    https://en.wikipedia.org/wiki/NaN

    Also you even have different kinds of NaN (signalling vs quiet)

  • Per IEEE 754, yes, but JS the language doesn't distinguish between NaN representations.
  • NaNs aren't always equal to each other in their bit representation either, most of the bits are kept as a "payload" which is not defined in the spec it can be anything. I believe the payload is actually used in V8 to encode more information in NaNs (NaN-boxing).
  • You’ve opened a rabbit hole for me
  • Shouldn't an operator on incompatible types return undefined? ;)

    Equality on things that it doesn't make sense to compare returning false seems wrong to me. That operation isn't defined to begin with.

    By shipping with undefined, JavaScript could have been there only language whose type system makes sense... alas!

  • "return undefined" is incoherent in almost every language, and IEEE754 predates JavaScript by a decade.
  • 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
    by agos
  • This reminds me of an interesting approach a student had to detecting NaNs for an assignment. The task was to count no-data values (-999) in a file. Pandas (Python library) has its own NaN type, and when used in a boolean expression, will return NaN instead of true or false. So the student changed -999 to NaN on import with Pandas and had a loop, checking each value against itself with an if statement. If the value was NaN the if statement would throw an exception (what could poor if do with NaN?) which the student caught, and in the catch incremented the NaN count.
  • > 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.

  • 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

  • The D language default initializes floating point values to NaN. AFAIK, D is the only language that does that.

    The rationale is that if the programmer forgets to initialize a float, and it defaults to 0.0, he may never realize that the result of his calculation is in error. But with NaN initialization, the result will be NaN and he'll know to look at the inputs to see what was not initialized.

    It causes some spirited discussion now and then.

  • In the same spirit, the `char` type default initializes to 0xFF, which is an invalid Unicode value.

    It's the same idea for pointers, which default initialize to null.

  • > That’s also the reason NaN !== NaN. If NaN behaved like a number and had a value equal to itself, well, you could accidentally do math with it: NaN / NaN would result in 1,

    So, by that logic, if 0 behaved like a number and had a value equal to itself, well, you could accidentally do math with it: 0 / 0 would result in 1...

    But as it turns out, 0 behaves like a number, has a value equal to itself, you can do math with it, and 0/0 results in NaN.

  • Try subtraction. But also, not all calculations are purely using mathematical operations. You might calculate two numbers from two different code paths and compare them.
  • > That’s also the reason NaN !== NaN. If NaN behaved like a number and had a value equal to itself, well, you could accidentally do math with it: NaN / NaN would result in 1, and that would mean that a calculation containing a NaN result could ultimately result in an incorrect number rather than an easily-spotted “hey, something went wrong in here” NaN flag.

    While I'm not really against the concept of NaN not equaling itself, this reasoning makes no sense. Even if the standard was "NaN == NaN evaluates to true" there would be no reason why NaN/Nan should necessarily evaluate to 1.