[기술 딥다이브] 0.1 + 0.2 ≠ 0.3인 이유: 부동소수점


한 줄 요약: 0.1 + 0.2가 0.30000000000000004가 되는 이유는 두 수가 2진법에서 무한소수라서, 유한 비트(IEEE 754)에 담는 순간 반올림 오차가 생기기 때문이다.

대부분의 프로그래밍 언어에서 0.1 + 0.2를 계산하면 0.3이 아니라 0.30000000000000004가 나옵니다. 이것은 버그가 아니라 부동소수점의 본질입니다. 왜 이런 일이 발생하는지 풀어 설명하겠습니다.

10진법의 0.1은 2진법으로 딱 떨어지지 않는다

컴퓨터는 수를 2진법으로 저장합니다. 0.1을 2진 소수로 변환하면 0.0001100110011...처럼 무한 반복하는 소수가 됩니다. 2진법은 1/2, 1/4, 1/8의 합으로 수를 표현하는데, 0.1(= 1/10)은 2의 거듭제곱의 합으로 정확히 표현할 수 없습니다. 이것은 10진법에서 1/3 = 0.3333...이 되는 것과 같은 원리입니다.

IEEE 754: 유한 비트에 무한소수를 담다

컴퓨터는 0.1을 64비트(double) 부동소수점에 담습니다. IEEE 754 배정도 형식은 부호 1비트 + 지수 11비트 + 가수 52비트로 구성됩니다. 0.1은 무한소수이므로 52비트 안에 전부 담지 못하고 반올림되어 아주 미세하게 다른 값으로 저장됩니다. 0.2도 마찬가지입니다. 둘을 더하면 그 작은 오차가 합쳐져서 눈에 보이게 되는 것입니다.

정확한 값: 0.30000000000000004

JavaScript, Python 등 대부분의 언어에서 0.1 + 0.20.30000000000000004입니다. 0.1 + 0.2 === 0.3false를 반환합니다. 이것은 언어의 버그가 아니라 IEEE 754를 준수하는 모든 시스템의 정상적인 동작입니다.

왜 이렇게 설계했는가: 속도와 범용성

부동소수점은 매우 넓은 범위의 값을(아주 작은 수부터 아주 큰 수까지) 고정 비트로 빠르게 처리하기 위해 만든 타협입니다. 십진 정확 계산보다 ‘대략 맞게, 빠르게, 넓은 범위를’ 선택한 것입니다. 그래서 과학 계산과 그래픽에는 적합하지만, 금전 계산에는 부적합합니다.

그러면 금전 계산은 어떻게: 정수나 십진 타입

금액처럼 십진 정확도가 중요한 경우에는 다음 방법을 사용합니다: (1) 센트 단위 정수로 계산(예: 1000원 = 100000센트), (2) 십진 전용 타입(JavaScript의 Decimal, Python의 decimal.Decimal, Java의 BigDecimal), (3) 표시할 때만 반올림. 절대 float로 금전을 처리해서는 안 됩니다.

비교할 때는 ’오차 허용’으로

0.1 + 0.2 == 0.3 대신, Math.abs(a - b) < 1e-9처럼 ’충분히 가까우면 같다’로 비교합니다. 이것이 부동소수점 비교의 표준 방식입니다.

어떻게 연결되는가

한 줄로 요약하면: 0.1과 0.2가 2진 무한소수이므로 유한 비트(IEEE 754)에 담으면 반올림 오차가 발생하고, 둘을 더했을 때 그 오차가 0.30000000000000004로 드러난다는 것입니다. 컴퓨터가 ’10진법’이 아니라 ’2진법’으로 수를 표현한다는 사실 하나에서 비롯된, 가장 유명한 ’버그 같은 정상 동작’입니다.

출처: 위키백과: IEEE 754 · 위키백과: 부동소수점 산술 · 0.30000000000000004.com

이미지: Sss41, Wikimedia Commons (CC-BY-SA 3.0) — 원본