Modern JavaScript: isNaN
Welcome to the isNaN lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
Most programming languages support many types of numbers: at minimum, integers and floating point, but often decimal, rational, and complex numbers as well. JavaScript is different: all JavaScript numbers are floating point. Specifically, they're double-precision floating point as defined in the IEEE (pronounced "I-triple-E") 754 standard, originally published in 1985.
IEEE 754 floating point numbers are surprisingly complex, with many subtle details. (The 2008 revision of the IEEE 754 standard is 70 pages long!) In this lesson, we'll focus on just one floating point issue that causes a lot of problems: NaN.
In floating point numbers, NaN means "not a number". IEEE 754's authors intended for
NaNto be used in rare cases. For example,0/0has no value, so they said that it should returnNaN. Unfortunately, JavaScript returnsNaNin many relatively common situations, often due to programming mistakes.For instance, any arithmetic on
undefinedreturnsNaN.>
undefined - 1;Result:
Here's a mistake where we typo
length(a real method on arrays) aslenght.>
['a', 'b', 'c'].lenght;Result:
>
['a', 'b', 'c'].lenght - 1;Result:
NaN
Any operation on a
NaNreturns anotherNaN. Unfortunately, that means thatNaNs will propagate often through your software system. When you actually see aNaN, it might be a symptom of a bug in a completely different part of the system.(Note that the next code example intentionally contains the same
lenghttypo that we made above.)>
const lastIndex = ['a', 'b', 'c'].lenght - 1;const middleIndex = Math.floor(lastIndex / 2);middleIndex;Result:
NaN
Even detecting the presence of a
NaNis surprisingly difficult.NaNis the only value in JavaScript that isn't equal to itself. Even when compared using===,NaNis never equal toNaN!>
NaN == NaN;Result:
>
NaN === NaN;Result:
false
Although
NaNisn't equal to itself, we can check for it with theisNaNfunction. This does exactly what its name suggests: it tells us whether a value is aNaN.>
isNaN(0);Result:
>
isNaN(Infinity);Result:
false
>
isNaN(NaN);Result:
true
Unfortunately,
isNaNalso converts its argument to a number before checking. When we ask it whetherisNaN(x)is true, we can imagine that we're really doingisNaN(0+x).>
0 + undefined;Result:
>
isNaN(0 + undefined);Result:
true
>
isNaN(undefined);Result:
true
undefinedmost definitely is notNaN, so this is an unambiguous mistake in JavaScript's design. But theisNaNfunction can't be removed or changed without breaking billions of lines of existing code, so this mistake remains in place.Instead, newer versions of JavaScript have introduced a second
isNaNfunction,Number.isNaN. Fortunately, this function behaves as we expect in every case.>
Number.isNaN(NaN);Result:
true
>
Number.isNaN(0);Result:
false
>
Number.isNaN(undefined);Result:
false
This may all seem confusing. That's because it is!
Strictly speaking, this
NaNbehavior is part of IEEE 754 floating point, which is used in virtually all computers today. IEEE 754's authors made these decisions for good reasons. For example: in the name of consistency, every mathematical operation should return some numerical value. That's even true of "nonsense" operations like0/0.However, any given programming language can choose how it handles edge cases like
0/0. Languages don't have to expose IEEE 754'sNaNbehavior to us. Many languages, including Python, throw an error when we try to do0/0. In JavaScript,0/0directly gives us the dreadedNaN, leading to the problems we saw above. Opinions differ on whether this is a design mistake in JavaScript.On the other hand, the existence of two
isNaNfunctions is definitely a mistake in JavaScript's design. If you've encountered these functions in the past and wondered whether you were missing something, you weren't. The originalisNaNfunction was a mistake, andNumber.isNaNshould be preferred in all circumstances.It's difficult to remember that there are two
isNaNs and that one of them is preferable. Often, we can use linters to ensure that we don't mix these things up, but the eslint linter doesn't have direct support for disallowing the globalisNaN. Fortunately, you can configure the no-restricted-globals rule to disallowisNaN, which will have the same effect.