자바스크립트 0.1 + 0.2 !== 0.3 해결 방법과 부동 소수점 오차의 원인
자바스크립트 콘솔창에 0.1 + 0.2 === 0.3을 입력하면 결과는 false가 나옵니다. 실제 연산 결과는 0.30000000000000004이기 때문이죠. 이는 자바스크립트의 결함이 아니라, 컴퓨터가 숫자를 처리하는 IEEE 754 부동 소수점(Floating Point) 표준 방식 때문에 발생하는 현상입니다.
돈을 계산하는 커머스 서비스나 정밀한 수치가 필요한 대시보드에서 이 오차를 방치하면 큰 사고로 이어질 수 있습니다. 오늘은 이 오차가 왜 발생하는지, 그리고 실무에서 어떻게 해결하는지 완벽하게 정리해 보겠습니다.
목차
- 왜 0.1 + 0.2는 0.3이 아닐까? (IEEE 754)
- 방법 1: 소수점 자릿수 제한 (toFixed, toPrecision)
- 방법 2: 정수로 변환하여 계산하기 (가장 권장됨)
- 방법 3: Number.EPSILON 활용하기
- 방법 4: 외부 라이브러리 사용 (Big.js, Decimal.js)
- 자주 묻는 질문(FAQ)
왜 0.1 + 0.2는 0.3이 아닐까? (IEEE 754)
컴퓨터는 모든 숫자를 2진수(0과 1)로 저장합니다. 문제는 우리가 사용하는 10진수 소수 중 상당수가 2진수로 변환했을 때 무한 소수가 된다는 점입니다.
- 10진수 0.1을 2진수로 바꾸면
0.0001100110011...처럼 끝없이 반복됩니다. - 컴퓨터의 메모리는 유한하므로 특정 지점에서 이 숫자를 반올림하거나 절삭합니다.
- 이렇게 미세하게 왜곡된 0.1과 0.2를 더하니 끝자리에
4가 붙는 오차가 발생하는 것입니다.
방법 1: 소수점 자릿수 제한 (toFixed, toPrecision)
단순히 화면에 보여주는 것이 목적이라면 가장 간단한 방법입니다.
const result = 0.1 + 0.2;
// toFixed()는 문자열을 반환하므로 다시 숫자로 바꿔야 할 때가 많습니다.
console.log(result.toFixed(1)); // "0.3" (string)
console.log(Number(result.toFixed(12))); // 0.3 (number)
주의: toFixed()는 반올림 규칙이 상황에 따라 다를 수 있어 정밀한 금융 계산에는 부적합할 수 있습니다.
방법 2: 정수로 변환하여 계산하기 (가장 권장됨)
소수점을 없애기 위해 적당한 수를 곱해 정수로 만든 뒤 연산하고, 다시 나누는 방식입니다. 오차가 없는 가장 확실한 방법 중 하나입니다.
/**
* 소수점 오차 없는 덧셈
*/
function safeAdd(a: number, b: number): number {
const multiplier = 10; // 소수점 첫째 자리까지 있다면 10을 곱함
return (a * multiplier + b * multiplier) / multiplier;
}
console.log(safeAdd(0.1, 0.2)); // 0.3
💡 개발자의 팁: 정수 변환의 한계
곱하는 수가 너무 커져서
Number.MAX_SAFE_INTEGER($$2^53 - 1$$)를 넘어가면 정수에서도 오차가 발생합니다. 아주 큰 금액이나 정밀도를 다룬다면BigInt나 전용 라이브러리를 고려해야 합니다.
방법 3: Number.EPSILON 활용하기
ES6에서 도입된 Number.EPSILON은 자바스크립트에서 표현할 수 있는 1과 1보다 큰 최소값 사이의 차이를 나타냅니다. 즉, “허용 가능한 오차 범위”로 이해하면 됩니다.
function areEqual(a: number, b: number): boolean {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(areEqual(0.1 + 0.2, 0.3)); // true
방법 4: 외부 라이브러리 사용 (Big.js, Decimal.js)
금융권 프로젝트나 복잡한 수식이 들어가는 앱이라면 직접 구현하기보다 검증된 라이브러리를 사용하는 것이 정신 건강에 좋습니다.
import Big from 'big.js';
const a = new Big(0.1);
const b = new Big(0.2);
const result = a.plus(b);
console.log(result.toString()); // "0.3"
- Big.js: 가볍고 빠름. 기본적인 연산 위주.
- Decimal.js: 삼각함수, 로그 등 복잡한 수학 연산 지원.
- bignumber.js: 암호화폐 관련 프로젝트에서 자주 쓰임.
실무 트러블슈팅: 백엔드와의 통신
실무에서 가장 흔한 문제는 백엔드에서 보내준 정밀한 숫자가 프론트엔드에서 파싱될 때 깨지는 것입니다.
⚠️ 보안 및 안정성 팁
64비트 정수(Long)를 사용하는 백엔드 데이터를 자바스크립트의
number로 받으면 숫자가 뭉개질 수 있습니다. 이럴 때는 백엔드에 요청하여 숫자를 문자열(String)로 받거나, 프론트엔드에서BigInt를 사용하여 파싱해야 데이터 손실을 막을 수 있습니다.
자주 묻는 질문(FAQ)
Q. 정수 연산도 오차가 생길 수 있나요?
자바스크립트의 number는 내부적으로 64비트 부동 소수점이므로, 약 9000조($$2^53-1$$)를 넘어가면 정수조차 정확히 표현하지 못합니다. 이때는 반드시 BigInt를 사용하세요.
Q. Math.round()로 해결하면 안 되나요?
단순 반올림은 0.1 + 0.2에는 작동할지 몰라도, 1.005를 1.01로 반올림해야 하는 상황 등에서 오동작하는 경우가 많습니다.
Q. 왜 모든 언어가 이런 문제를 가지고 있나요?
C, Java, Python 등 대부분의 언어가 동일한 IEEE 754 표준을 따르기 때문입니다. 다만 언어마다 이를 처리하는 별도의 데이터 타입(Decimal 등)을 기본 제공하느냐의 차이입니다.
더 깊이 있는 부동 소수점 이야기는 IEEE 754 공식 문서를 참고해 보세요.
댓글을 불러오는 중...