부동소수점

실수를 부호·지수·가수로 나눠 이진수로 저장하는 방식으로, 유한한 비트로 무한한 실수를 표현하기 때문에 필연적으로 오차가 생깁니다.

자바스크립트의 숫자 표현

자바스크립트에는 정수 타입이 따로 없고, Number 타입 하나가 IEEE 754 표준의 64비트 배정밀도 부동소수점을 사용합니다.

구성비트 수역할
부호(sign)1양수/음수
지수(exponent)11소수점 위치
가수(mantissa)52유효 숫자

가수가 52비트로 고정되어 있으므로, 이진수로 딱 떨어지지 않는 소수는 근사값으로 잘려서 저장됩니다.

왜 0.1 + 0.2 !== 0.3 인가

10진수 0.1과 0.2를 2진수로 바꾸면 0.0001100110011...처럼 순환소수가 됩니다. 2진수로 유한하게 표현할 수 있는 소수는 분모가 2의 거듭제곱인 값(0.5, 0.25, 0.125 …)뿐이기 때문입니다.

저장 시점에 이미 근사값이 되고, 두 근사값을 더하면 오차가 누적됩니다.

console.log(0.1 + 0.2);              // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);      // false
console.log((0.1).toFixed(20));      // "0.10000000000000000555"

===는 두 값을 있는 그대로 비교하므로 0.30000000000000004와 0.3은 다른 값으로 판정됩니다.

오차를 다루는 방법

1. 자릿수를 정해 반올림

(0.1 + 0.2).toFixed(2);        // "0.30"  ← 문자열로 변환됨
+(0.1 + 0.2).toFixed(2);       // 0.3

가장 단순하지만 결과가 문자열이 되므로 다시 숫자로 변환해야 합니다.

2. 허용 오차(엡실론)로 비교

Number.EPSILON은 1과, 1보다 큰 수 중 표현 가능한 가장 작은 수의 차이(약 2.22e-16)입니다. 두 값의 차이가 이보다 작으면 같다고 간주합니다.

function isEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}

console.log(isEqual(0.1 + 0.2, 0.3)); // true

다만 값이 커질수록 표현 간격도 커지므로, 큰 수를 비교할 때는 상대 오차를 써야 합니다.

function isEqual(a, b) {
  return Math.abs(a - b) <= Number.EPSILON * Math.max(Math.abs(a), Math.abs(b), 1);
}

3. 정수로 바꿔 계산

금액처럼 오차가 허용되지 않는 값은 최소 단위(원, 센트)의 정수로 바꿔 다루거나, BigInt 혹은 decimal.js 같은 십진 연산 라이브러리를 사용합니다.

(0.1 * 100 + 0.2 * 100) / 100;            // 0.30000000000000004 → 곱셈에서도 오차가 남는다
Math.round(0.1 * 100 + 0.2 * 100) / 100;  // 0.3

주의사항

Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true

핵심 정리

→ 자바스크립트에서 0.1 + 0.2 === 0.3의 실행 결과에 대해 설명해주세요

CSLOG에서 보기