← Debugging Guides

阅读中文版本

JavaScript NaN Comparisons — NaN !== NaN

Handle NaN correctly by using Number.isNaN, understanding that NaN is not equal to itself, and validating numeric input at the boundary.

beginnerdebuggingjavascriptnannumbers

Symptoms

  • A value never equals itself in a comparison
  • Array.indexOf(NaN) returns -1 even though NaN is present
  • isNaN("abc") is true but the value "abc" is a string, not NaN

Possible causes

  • NaN is compared with === or !==, which never matches
  • Global isNaN coerces its argument, giving false positives for non-numeric strings
  • Arithmetic produced NaN (0/0, invalid parse) and it propagated silently

Diagnosis steps

Demonstrate the identity trap

node -e "const n=NaN; console.log(n===n, Object.is(n, n), Number.isNaN(n))"

Interpretation

n === n is false; Object.is(NaN, NaN) and Number.isNaN(n) are true.

Compare global isNaN with Number.isNaN

node -e "console.log(isNaN('abc'), Number.isNaN('abc'))"

Interpretation

Global isNaN coerces and returns true for "abc"; Number.isNaN returns false because it is not the number NaN.

Root cause

Per IEEE 754, NaN is not equal to any value including itself, so === and indexOf never match NaN. The global isNaN coerces its argument while Number.isNaN tests specifically for the NaN value.

Fix

Use Number.isNaN for the NaN value, Object.is for equality that treats NaN as equal, and validate inputs so NaN does not enter calculations; use Number.isFinite to require a real number.

Risk note

Global isNaN on non-number values is misleading; always prefer Number.isNaN for the value and typeof checks for the type.

Verify the fix

Assert Number.isNaN(NaN) is true and Number.isNaN("abc") is false in tests; verify Object.is treats two NaNs as equal.

Symptoms

When you hit “JavaScript NaN Comparisons — NaN !== NaN”, guessing wastes time. The guide below follows evidence: reproduce the symptom, run targeted diagnostics, identify the root cause, apply the fix, and verify it with concrete checks.

Observed symptoms include:

  • A value never equals itself in a comparison
  • Array.indexOf(NaN) returns -1 even though NaN is present
  • isNaN(“abc”) is true but the value “abc” is a string, not NaN

Reproduce and Diagnose

Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.

Step 1: Demonstrate the identity trap

node -e "const n=NaN; console.log(n===n, Object.is(n, n), Number.isNaN(n))"

n === n is false; Object.is(NaN, NaN) and Number.isNaN(n) are true.

Step 2: Compare global isNaN with Number.isNaN

node -e "console.log(isNaN('abc'), Number.isNaN('abc'))"

Global isNaN coerces and returns true for “abc”; Number.isNaN returns false because it is not the number NaN.

Likely contributors, in order of frequency:

  • NaN is compared with === or !==, which never matches
  • Global isNaN coerces its argument, giving false positives for non-numeric strings
  • Arithmetic produced NaN (0/0, invalid parse) and it propagated silently

Root Cause

Per IEEE 754, NaN is not equal to any value including itself, so === and indexOf never match NaN. The global isNaN coerces its argument while Number.isNaN tests specifically for the NaN value.

The Fix

Use Number.isNaN for the NaN value, Object.is for equality that treats NaN as equal, and validate inputs so NaN does not enter calculations; use Number.isFinite to require a real number.

Watch out: Global isNaN on non-number values is misleading; always prefer Number.isNaN for the value and typeof checks for the type.

const n = 0 / 0; // NaN

console.log(n === NaN);        // false (always)
console.log(Number.isNaN(n));  // true
console.log(Object.is(n, NaN));// true

// array membership with NaN
const arr = [NaN, 1];
console.log(arr.includes(NaN)); // true (SameValueZero)
console.log(arr.indexOf(NaN));  // -1 (=== comparison)

Verify and Prevent

Assert Number.isNaN(NaN) is true and Number.isNaN(“abc”) is false in tests; verify Object.is treats two NaNs as equal.

Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.

This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.