Why you should not use isNaN in JavaScript


I was on a video streaming site recently and moved the play point to the far right. It was amusing to see the hover details show NaN:NaN – ahah, some mathematical operation had NaN-ed and the code didn’t cater for that.

If you have read Why JavaScript ‘seems’ to get addition wrong; you would have seen that some operations do result in a NaNNaN is a value with its roots in the IEEE754 standard definition.

What is NaN?

Nan literally means Not a Number. Yes, it means that value is not a number and occurs when you try to coerce a non-mathematical value (e.g. string) into a number.

How do you check if a value is NaN

How do you know if some value is NaN? Turns out this is not so straightforward.

For numbers, we typically compare to the expected value and that is usually true; however the case for NaN is different.

let two = 2;
two === 2; // true
two == 2; // true

// NaN
let x = NaN;
x === NaN; // false
x == NaN; // false
x === x; // false ???

Unequal ‘equalities’ in Maths and JavaScript

You might be scratching your head and wondering if there are other values that can be unequal. Yes, there is one – Infinity. In Mathematics, infinite values are not equal even if most operations assume this for simplicity.

Imagine two containers of water – a large jug and a small cup. Both contain infinite amounts of atoms right? Yet, it is obvious that the infinite amount of atoms in the large jug is greater than the infinite amount of atoms present in the small cup. The inability to determine a specific value doesn’t automatically make all infinite values equal.

Thus, even though the result of 1 * ∞ and 10 * ∞ are both ∞ in most languages; we can argue the latter is a ‘larger’ type of ∞. It might not matter so much given that computers have finite storage limits. For a more in-depth discussion of this, read Jeremy Kun’s excellent post.

Let’s see if JavaScript obeys this Maths law.

let infinity = Infinity;
infinity === Infinity; // true

(2 * Infinity) === (10 * Infinity); // true

So JavaScript coalesces all Infinity values and makes them ‘equal’. But NaN is exempt from this as shown earlier.

The good thing is that this special quality of NaN stands out. According to the IEEE754 standard, NaN cannot be equal to anything (even itself). Thus to determine if a value is NaN, you can check if that value is not equal to itself.

let nan = NaN;
nan === nan; // false
nan !== nan; // true

The Issue with JavaScript’s isNaN

JavaScript exposes the isNaN method for checking for NaN values. The snag however is that it behaves unreliably with varying operand types.

isNaN(NaN); // true
isNaN(2); // false
isNaN('a'); // true
isNaN(); // true
isNaN(null); // false
isNaN(true); // false

Surprised? Again, this is the exhibition of one of JavaScript’s quirks. The spec reads thus:

Returns true if the argument coerces to NaN, and otherwise returns false.

And what’s the toNumber coercion table?

Value Numeric value
null 0
undefined NaN
true 1
false 0
123 123
[] 0
{} NaN

So you now know why isNaN() and isNaN({a: 1}) are both true even though isNaN([]) is false. Even though arrays are objects, their toNumber coercion is not NaN (as shown in the table above). Similarly since the boolean primitives coerce to numbers; calling isNaN(true) or isNaN(false) will give a false outcome.

Reliably verifying NaN values

There are two fixes to this

1. Prior to ES6, the only way is to check if the value is not equal to itself.

function isReliablyNaN(x) {
    return x !== x;
}

2. ES6 introduces the Number.isNaN method which avoids the inherent toNumber coercion of isNaN. This ensures that only NaN returns true.

Number.isNaN(NaN); // true

// All work as expected now
Number.isNaN(2); // false
Number. isNaN('a'); // false
Number. isNaN(); // false
Number. isNaN(null); // false
Number.isNaN(true); // false

Conclusion

If you are using isNaN in your code; you most likely have a bug waiting to happen some day some time.

You should switch to Number.isNaN which is already supported by all major browsers except IE11 and also add a polyfill fallback (just in case). You should also know that isFinite uses isNaN internally and consequently suffers the same flaws. Use Number.isFinite instead.

I would have wanted a reliable isNaN implementation but alas the special characteristic has now become a ‘feature’ and can’t even be fixed for backwards compatibility reasons.

Related

If you enjoyed this post and wanted to learn more; here are a couple of posts explaining various quirky behaviours in JavaScript.

  1. Why JavaScript ‘seems’ to get addition wrong
  2. Why JavaScript has two zeros: -0 and +0
  3. JavaScript has no Else If
  4. Quirky Quirky JavaScript: Episode One
Advertisements