부동소수점 오차는 왜 발생하는가
컴퓨터에서 실수를 다룰 때 가장 자주 마주치는 오해 중 하나는 0.1, 0.2, 0.3 같은 숫자가 당연히 정확하게 계산될 것이라는 기대다. 하지만 실제로는 다음과 같은 결과를 보게 된다.
1
0.1 + 0.2 == 0.3 # False
이 현상은 언어나 라이브러리의 버그가 아니라, 부동소수점이 숫자를 표현하는 방식 자체에서 비롯된다.
핵심 원인: 실수를 근사치로 저장한다
부동소수점은 실수를 “있는 그대로” 저장하지 않는다. 정해진 비트 수 안에서 부호, 지수, 가수를 나눠 담고, 그 안에서 표현 가능한 가장 가까운 값으로 반올림해 저장한다.
즉, 문제의 핵심은 두 가지다.
- 모든 10진 소수를 2진수로 정확히 표현할 수 없다.
- 저장 가능한 비트 수가 유한하다.
1. 10진 소수는 2진수에서 무한 반복될 수 있다
우리가 익숙한 0.1은 10진수에서는 간단하지만, 2진수에서는 무한 반복 소수가 된다.
1
0.1 = 0.00011001100110011...
이 값을 메모리에 그대로 다 저장할 수는 없다. 결국 어느 지점에서 끊고 근사값을 저장하게 된다.
이 때문에 0.1, 0.2, 0.3 모두 사람이 기대하는 값과 완전히 일치하지 않는 내부 표현을 가진다.
2. 정밀도에는 한계가 있다
대표적으로 IEEE 754 표준의 float와 double은 각각 정해진 비트 수를 가진다.
| 타입 | 가수부 비트 | 대략적인 10진 정밀도 |
|---|---|---|
float | 23비트 | 약 7자리 |
double | 52비트 | 약 15~16자리 |
즉, 숫자를 아무리 정교하게 계산하더라도 표현 가능한 비트 수를 넘는 순간 오차가 발생한다.
그래서 어떤 문제가 생기나
가장 대표적인 문제는 세 가지다.
비교 연산이 기대대로 동작하지 않는다
1
2
double a = 0.1 + 0.2;
System.out.println(a == 0.3); // false
이 경우 a와 0.3은 사람이 보기에는 같지만, 내부적으로는 약간 다른 값일 수 있다.
누적 오차가 커질 수 있다
작은 오차도 반복 연산을 거치면 눈에 띄게 누적될 수 있다.
1
2
3
4
total = 0.0
for _ in range(10):
total += 0.1
print(total) # 0.9999999999999999
금융/정산 같은 영역에서는 치명적일 수 있다
금액, 수수료, 비율 계산처럼 “한 자리의 오차도 허용하기 어려운” 영역에서는 부동소수점을 그대로 사용하는 것이 위험하다.
내부적으로는 어떻게 해석되나
부동소수점은 보통 다음 구조를 따른다.
1
(-1)^sign × 1.mantissa × 2^(exponent - bias)
sign: 부호mantissa: 유효 숫자exponent: 지수bias: 지수를 저장하기 위한 보정값
이 구조 덕분에 매우 크거나 매우 작은 수를 표현할 수 있지만, 그 대신 일부 값은 정확도가 희생된다.
실무에서 어떻게 다뤄야 하나
1. ==로 직접 비교하지 않는다
실수 비교는 보통 허용 오차를 둔다.
1
Math.abs(a - b) < 1e-9
이 방식은 두 값이 충분히 가까우면 같은 값으로 간주한다.
2. 금액 계산에는 BigDecimal 같은 타입을 쓴다
정확한 10진 계산이 필요한 경우에는 부동소수점 대신 BigDecimal 같은 타입을 써야 한다.
1
2
3
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // 0.3
중요한 점은 문자열로 생성해야 한다는 것이다. new BigDecimal(0.1)처럼 생성하면 이미 부동소수점 오차가 반영된 값을 가져오게 된다.
3. 정수 스케일링도 유효한 방법이다
돈처럼 소수 자릿수가 고정된 경우에는 아예 정수로 환산해서 계산할 수 있다.
예:
- 10.25원 -> 1025
- 단위: 0.01원
이렇게 하면 비교와 합산이 훨씬 단순해진다.
정리
부동소수점 오차는 예외적인 상황이 아니라 기본 동작이다.
- 10진 소수를 2진수로 정확히 표현할 수 없는 경우가 많고
- 표현 가능한 비트 수는 유한하며
- 그 결과 계산과 비교에서 미세한 차이가 생긴다
그래서 실수를 다룰 때는 항상 질문해야 한다.
- 이 값은 근사치여도 되는가
- 직접 비교해도 되는가
- 정밀한 10진 계산이 필요한가
부동소수점은 빠르고 범용적이지만, 정확성을 보장하는 도구는 아니다. 숫자의 의미가 중요한 도메인일수록 이 차이를 명확히 이해하고 타입을 선택해야 한다.
댓글
아직 댓글이 없습니다