Github Pages
자바스크립트에서 `NaN !== NaN`인 이유 (그리고 그 뒤에 숨은 IEEE 754 이야기)
NaN !== NaN 은 자바스크립트의 변덕이 아니다. 1985년 IEEE 754 가 *프로그램이 죽지 않고 계산을 이어가도록* 채택한 의도된 설계로, 하드웨어의 ucomisd 명령어까지 거슬러 올라간다.
핵심 요약
(1) NaN !== NaN 은 의도된 설계 — isnan() API 가 없던 시절 x != x 트릭으로 감지하라고. (2) 하드웨어 수준 — x86 의 ucomisd 명령어가 비교 시 PF(Parity Flag) 를 세팅해 NaN 이면 jnp 가 점프하지 않는다. CPU 가 직접 처리하는 만큼 빠르고, 모든 언어가 동일하게 동작. (3) NaN 의 비트 패턴 — IEEE 754 double 의 지수가 0x7FF 이고 가수가 0 이 아닌 모든 값. quiet NaN 과 signaling NaN 두 종. (4) 자바스크립트의 가드 선택:
value !== value— 빠른 트릭, 가독성 떨어짐isNaN(value)— 문자열도 변환 후 검사 → 위험Number.isNaN(value)— 진짜 NaN 만 → 권장Object.is(value, NaN)— 동작은 같으나 의도 명확. (5) 왜 NaN 이 필요한가 — 0/0 같은 정의되지 않은 연산에서 프로그램이 죽지 않고 계산을 이어가도록 한 결정. NaN 은 산술 연산을 통해 전파되므로 마지막에 한 번만 검증 해도 안전.
NaN 을 '자바스크립트의 이상한 값' 이 아니라 'IEEE 754 부동소수점 표준이 정의한 — 하드웨어 단계에서 처리되는 — 오류 표현 값' 으로 본다. NaN !== NaN 은 isnan() 함수가 없던 시절 x != x 로 NaN 을 감지하기 위한 의도된 설계이며, typeof NaN === 'number' 인 이유는 NaN 이 별도 타입 이 아니라 숫자 시스템의 일부 이기 때문이다.
이 이해가 없으면 NaN 가드 를 잘못 짜고, 폼 검증/계산 로직에서 조용히 깨지는 코드를 만든다.
value === NaN은 항상 false — 평범한 비교 연산자로는 NaN 을 절대 잡을 수 없다isNaN()은 문자열도 강제 변환 후 검사라isNaN('foo')가 true → 실수의 원인Number.isNaN()은 순수하게 NaN 만 — 안전한 가드의 디폴트parseInt('abc'),0/0,Math.sqrt(-1),∞ - ∞가 모두 NaN 을 만들 수 있고, 그 NaN 은 모든 산술 연산을 통해 전파 된다- 이 전파성 덕분에 한 번 NaN 이 끼면 계산 끝까지 살아남으므로 배치 결과만 마지막에 검증 해도 안전
면접에서 왜 NaN 이 number 인가 는 표면 질문이고, NaN 가드의 안전한 방법 까지 답할 수 있어야 한다.
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
안전한 NaN 가드는 `Number.isNaN`
전역 isNaN() 은 인자를 Number 로 강제 변환 한 뒤 검사하기 때문에 문자열도 NaN 으로 판정한다.
isNaN('foo'); // true — 'foo' → NaN 으로 변환
isNaN(undefined); // true — undefined → NaN
isNaN({}); // true — {} → NaN
Number.isNaN('foo'); // false — 진짜 NaN 만
Number.isNaN(NaN); // true
Object.is(NaN, NaN); // true
// 트릭 (가독성 ↓)
const v = 0/0;
v !== v; // true
폼 입력 검증·외부 데이터 파싱에서 isNaN() 을 쓰면 문자열 입력을 모두 NaN 으로 분류 하는 버그가 생긴다.
'isNaN 이 짧으니 그게 표준' 이라고 생각하는 것. ES2015 부터 Number.isNaN 이 표준 가드다.
NaN 은 *전파* 된다 — 마지막에 한 번만 검증
산술 연산에 NaN 이 들어오면 결과는 무조건 NaN. 즉 중간 단계마다 가드 할 필요 없이 마지막에 한 번만 검증해도 모든 오류 경로가 잡힌다.
const total = parseFloat(input1) + parseFloat(input2) * tax;
if (Number.isNaN(total)) {
return showError('숫자만 입력하세요');
}
각 단계마다 if-NaN 을 박는 것보다 단순하다. 단, Math.max([]), Math.min([]) 처럼 NaN 이 아닌 다른 값 을 반환하는 케이스는 별도 가드 필요.
각 산술 단계마다 Number.isNaN 을 박는 것. 코드는 길어지고 성능은 차이 없는데 안전성은 동일하다.
`typeof NaN === 'number'` 의 의미
NaN 은 Number 타입 의 한 값이다. JS 의 number 는 IEEE 754 double 이고, 그 표준이 NaN 을 number 의 한 패턴 으로 정의했기 때문.
IEEE 754 double (64-bit):
부호(1) | 지수(11) | 가수(52)
지수가 0x7FF + 가수가 0 → ±Infinity
지수가 0x7FF + 가수 ≠ 0 → NaN (양/음, quiet/signaling 구분)
그래서 NaN, Infinity, -Infinity 는 모두 typeof === 'number' 이고 Number.isFinite 로만 진짜 유한 한 값을 가려낼 수 있다.
typeof x === 'number' 만으로 유효한 숫자 라고 판단하는 것. NaN/Infinity 가 통과한다 — Number.isFinite 가 더 안전한 가드.
읽는 순서
- 1이론
MDN 의
Number.isNaN/Number.isFinite/Object.is페이지를 비교하고 언제 어느 것을 쓰는지 표로 정리. - 2구현
ESLint
no-restricted-globals로 전역isNaN/isFinite사용을 금지해본다. 기존 위반을 모두Number.*로 교체. - 3실무
폼 입력 컴포넌트의 검증 로직을
Number.isFinite기반으로 통일하고, 단위 테스트로 비숫자 문자열 / Infinity / NaN 케이스를 모두 추가. - 4설명
팀에 'NaN 가드의 4가지 방법과 디폴트' 5분 발표.
===/isNaN/Number.isNaN/Object.is비교.
면접 연결 질문
[감점 답변] '전자가 더 관대하다'. [좋은 답변] 전자는 인자를 Number 로 강제 변환 후 검사 → 'foo' → NaN 이라 true. 후자는 진짜 NaN 일 때만 true. 폼/외부 데이터 검증에서는 Number.isNaN (또는 Object.is(x, NaN)) 이 안전한 디폴트.
[감점 답변] Number(input) 후 isNaN. [좋은 답변]
function parseNumeric(input) {
const n = Number(input);
if (!Number.isFinite(n)) return null; // NaN 도 Infinity 도 거름
return n;
}
isNaN 은 Infinity 를 통과시키고, Number.isNaN 은 '12px' → NaN 을 못 잡는다 (Number 변환 후 NaN 이긴 하지만, 함수 사용자 입장에선 유한 숫자 만 원한다).
자기 점검
'전역 isNaN 이 표준이다'. 사실 Number.isNaN 이 ES2015 부터의 안전 가드다.