A Number That Always Returns the Same Thing
Today we’ll look at the NaN type, which in JavaScript is identified as type number.
| |
We get the type number in response.
Since something is a number, logic suggests we can perform mathematical operations on it.
So let’s try adding something to it or checking its maximum or minimum value.
| |
As you can see, after adding, subtracting, checking the maximum and minimum values - we always get the same result.
If that’s the case, why do we need such a value?
To try to explain this, let’s look into Firefox or V8 to find the usage and implementation of NaN.
| |
Looking at the code of example browsers, the std::isnan method from the standard library is used to check for NaN, which may already suggest to us that this is something that appeared independently of JavaScript.
And indeed, looking historically, the first standardization of NaN appeared in 1985 and was given the number IEEE 754.
From JavaScript to Hardware Level
Armed with this knowledge, let’s write a simple program in C, where based on what we found in the browser code, we’ll check how NaN behaves.
| |
| |
The result is the same as in JavaScript!
| |
We already know that we encounter NaN in other programming languages as well
| |
| |
| |
but we still don’t know what it’s for.
And since we don’t know, let’s generate assembly code for our modest program (let’s skip the prologue and stack frame initialization).
| |
For those who haven’t had contact with assembly - what’s worth noting for us is the xmm0 register, which performs operations on floating-point numbers.
Which is logical: we want to perform an operation on numbers, the CPU operates on numbers, so it will be fastest to do this in registers specifically designed for this purpose!
We can also see the ucomisd instruction, which is responsible for setting a flag when it detects NaN.
What conclusion can we draw from this? NaN is implemented at the hardware level, not at the JavaScript abstraction level.
So - rewriting the program in assembly to avoid unnecessary abstractions, let’s examine its execution result:
| |
The result?
| |
The program’s output is the same as with high-level C.
We already know that NaN is natively implemented, so let’s look at the ucomisd instruction.
| |
ucomisd - or Unordered Compare Scalar Double-precision floating-point. This wonderful instruction saved time and nerves for programmers on the x86 architecture, because at the CPU level it already checks whether the result of operations on numbers is correct or not.
NaN !== NaN
The main reason was to provide programmers with a way to detect NaN using the x != x test in times when the isnan() function didn’t yet exist in programming languages.
From a logical point of view, this makes a lot of sense, because a non-value cannot equal a non-value.
This is intentional design, not a bug.
typeof NaN === “number”
NaN is part of the numeric system (IEEE 754), not a separate type. It’s a special numeric value signaling a mathematical operation error.
IEEE 754-1985: Standard for Binary Floating-Point Arithmetic
- Published: 1985
- Author: William Kahan (UC Berkeley) + IEEE committee
- Defines: NaN, Infinity, denormalized numbers, rounding modes
Key decisions:
NaN !== NaN(alwaysfalsewhen comparing for equality)- Exponent =
0x7FF, mantissa ≠ 0 - Quiet NaN (qNaN) - propagates through operations without signaling an exception
- Signaling NaN (sNaN) - generates an exception on first use in an operation
NaNpropagation (any operation withNaN→NaN)
NaN is a Number, But What Kind?
You might be surprised why floating-point number registers are used for dividing 0/0.
Operations on number type values in JavaScript are represented as double-precision floating-point numbers (double) to perform operations on them according to the IEEE 754 standard.
In integer operations, division by zero is an unambiguous error. However, in floating-point numbers, we have many cases that can lead to undefined results:
0.0 / 0.0→NaN∞ - ∞→NaN0 * ∞→NaNsqrt(-1)→NaN
Without the IEEE 754 standard, each hardware manufacturer dealt with these situations differently, resulting in enormous code portability problems.
1994: Pentium FDIV Bug
A bug in Pentium’s floating-point division - some divisions gave incorrect results. It wasn’t a problem with NaN, but it showed the importance of precise IEEE 754 implementation.
Intel replaced millions of processors, which cost the company $475 million.
NaN as the Savior of Programmers
We learned that NaN is set at the hardware level, but what was there before NaN?
Before the IEEE 754 standard (1985), each hardware manufacturer did it their own way, which usually meant that operations like 0/0 ended in a crash and program termination.
This required very defensive programming from developers. Imagine you’re flying in an airplane, and in the control system, a programmer didn’t anticipate 0/0 - the instruction executes on the CPU and crashes the entire program due to a Division Error!
Intel and other manufacturers were fed up with the chaos resulting from programs behaving differently on different architectures.
NaN (Not a Number)
We can ask ourselves why a special value instead of another solution.
Let’s consider different options:
Option A: Division Error → CRASH (existing before IEEE 754)
- Unexpected program termination (see airplane example)
- Requires defensive programming before every operation
Option B: Return, for example, 0
- Mathematically incorrect
- Masks the error
- Further calculations give false results
Option C: Return null or a special error code
- Requires checking after every operation
- Interrupts the chain of mathematical calculations
- The result type becomes inconsistent
Option D: Special value NaN (chosen by IEEE 754)
- Value propagates through calculations
- Program continues running
- Can check the result at the end
- Maintains type consistency (number)
What Would It Be Like Without NaN?
| |
What Do We Have Thanks to NaN?
| |
Summary
NaN is an elegant solution to the problem of error handling in floating-point calculations:
- Implemented at the hardware level (
ucomisdinstruction) - Part of the IEEE 754 standard since 1985
- Propagates through operations, allowing error detection at the end of calculations
NaN !== NaNis intentional design enabling detectiontypeof NaN === "number"because it’s part of the numeric system, not a separate type



