본문 바로가기
Etc2026년 8월 18일6분 읽기

IEEE 754 심화 — NaN, 무한대, 그리고 음의 0

YS
김영삼
조회 8
IEEE 754 심화 — NaN, 무한대, 그리고 음의 0

부동소수점을 쓰다 보면 숫자가 아닌 이상한 값들을 만납니다. NaN(Not a Number), Infinity(무한대), 그리고 놀랍게도 음의 0(-0.0)까지. 이것들은 버그가 아니라 IEEE 754 표준이 일부러 정의해 둔 특수값입니다. 0으로 나누거나 표현 범위를 넘는 연산이 프로그램을 죽이는 대신, 계산을 계속 이어갈 수 있게 만든 안전장치죠.

이 특수값들의 규칙을 모르면 "왜 이 조건문이 안 걸리지?", "정렬이 왜 깨지지?" 같은 미스터리에 시달립니다. 하나씩 정체를 밝혀보겠습니다.

비트는 어떻게 나뉘어 있나

64비트 double은 부호 1비트 + 지수 11비트 + 가수 52비트로 구성됩니다. 특수값은 지수 필드가 전부 1일 때 등장합니다. 그 안에서 가수가 0이냐 아니냐로 무한대와 NaN이 갈립니다.

지수 필드가수 필드
정상 수전부 1도 0도 아님임의
±무한대전부 1전부 0
NaN전부 10이 아님
±0전부 0전부 0

가수가 0이 아니기만 하면 전부 NaN이라, NaN을 나타내는 비트 패턴은 사실 엄청나게 많습니다. 이 여분의 비트에 진단 정보를 실어 나르는 기법(NaN 박싱)을 자바스크립트 엔진 같은 곳에서 씁니다.

NaN — 자기 자신과도 다른 값

NaN의 가장 악명 높은 성질은 이겁니다. NaN은 그 어떤 것과도, 심지어 자기 자신과도 같지 않습니다.

const x = 0/0;        // NaN (JS에서는 예외 대신 NaN)
x === x;              // false ‼
x === NaN;            // false — 이걸로는 절대 못 잡는다
Number.isNaN(x);      // true  — 올바른 검사법
[3, NaN, 1].sort();   // 정렬이 뒤죽박죽 — 비교가 전부 false라서

그래서 NaN 판별은 반드시 전용 함수(Number.isNaN, 파이썬 math.isnan, C의 isnan)로 해야 합니다. 재밌는 활용도 있어요. x !== x가 참이면 x는 NaN이라는 뜻이라, 이 관용구로 NaN을 검출하기도 합니다.

내가 겪은 최악의 NaN 버그는 통계 대시보드였습니다. 데이터 한 칸이 비어 평균에 NaN이 섞였는데, NaN은 어떤 비교든 false를 내니 if (avg > threshold)가 조용히 통과되지 않고, 그래프의 그 이후 지점이 통째로 사라졌죠. 원인 찾는 데 한나절 걸렸습니다. NaN은 조용히 전염됩니다 — 한 번 섞이면 이후 연산 결과가 전부 NaN이 되니까요.

무한대 — 오버플로의 안전한 착지점

표현 범위를 넘어서면(1e308 * 10) 프로그램이 죽는 대신 Infinity가 됩니다. 0이 아닌 수를 0으로 나눠도 부호에 맞는 무한대가 나오죠. 무한대는 NaN과 달리 비교가 정상 동작합니다. Infinity > 1e308은 참이에요. 그래서 "아직 최솟값을 못 정했음"의 초기값으로 Infinity를 자주 씁니다.

1 / 0;          //  Infinity
-1 / 0;         // -Infinity
Infinity - Infinity;  // NaN — 부정형은 NaN으로
Infinity / Infinity;  // NaN

무한대끼리의 부정형 연산(∞ - ∞, ∞ / ∞, 0 × ∞)은 정의할 수 없으니 NaN을 냅니다. 여기서 NaN이 튀어나오는 경로가 은근히 많습니다.

음의 0 — 있는데 없는 척하는 값

-0.0+0.0비트가 다른 별개의 값이지만, -0 === 0true로 나옵니다. 대부분의 상황에서 둘은 같게 취급되지만, 딱 한 군데서 차이가 드러납니다 — 나눗셈의 부호입니다.

-0 === 0;        // true (헷갈리게도)
1 / 0;           //  Infinity
1 / -0;          // -Infinity ‼  여기서 갈린다
Object.is(-0, 0) // false — 구분하려면 Object.is

-0은 "음의 방향에서 0으로 다가간 극한"을 담고 있습니다. 그래서 부호가 중요한 계산이나 복소수 함수에서 의미를 갖습니다. 구분이 필요하면 자바스크립트에선 Object.is를, 저수준에선 부호 비트를 직접 봐야 합니다.

자주 묻는 질문

왜 0으로 나눴는데 예외가 안 나나요?

정수 나눗셈은 대개 예외를 던지지만, IEEE 754 부동소수점 나눗셈은 표준상 무한대나 NaN을 반환하고 플래그만 세우도록 설계됐습니다. 수치 계산이 예외로 중단되지 않고 흐르게 하려는 의도입니다. 언어에 따라 이 플래그를 예외로 승격시킬 수도 있지만 기본은 조용한 특수값입니다.

JSON으로 NaN이나 Infinity를 보낼 수 있나요?

없습니다. JSON 표준에는 이 값들이 아예 없어서, 대부분의 직렬화기가 null로 바꾸거나 오류를 냅니다. API 경계에서 NaN이 null로 둔갑해 데이터가 조용히 사라지는 사고가 흔하니, 직렬화 전에 isFinite로 걸러내는 방어가 필요합니다.

NaN이 어디서 처음 생겼는지 어떻게 추적하나요?

NaN은 전염되므로 최종 결과만 보면 발원지를 알기 어렵습니다. 중간 단계마다 isNaN 검사를 넣거나, 하드웨어의 부동소수점 예외 트랩(예: C의 feenableexcept)을 켜서 NaN이 생성되는 순간 프로그램을 멈추게 하면 발원지를 바로 잡을 수 있습니다.

float 비교는 그럼 어떻게 해야 하나요?

등호(==)로 직접 비교하는 건 반올림 오차 때문에 위험합니다. 보통 두 값의 차이가 작은 허용오차(epsilon)보다 작은지로 비교합니다. 다만 이때도 한쪽이 NaN이면 모든 비교가 false가 되니, 비교 전에 NaN 여부를 먼저 확인하는 게 안전합니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록