포스트

부동소수점 오차는 왜 발생하는가

컴퓨터에서 실수를 다룰 때 가장 자주 마주치는 오해 중 하나는 0.1, 0.2, 0.3 같은 숫자가 당연히 정확하게 계산될 것이라는 기대다. 하지만 실제로는 다음과 같은 결과를 보게 된다.

1
0.1 + 0.2 == 0.3  # False

이 현상은 언어나 라이브러리의 버그가 아니라, 부동소수점이 숫자를 표현하는 방식 자체에서 비롯된다.

핵심 원인: 실수를 근사치로 저장한다

부동소수점은 실수를 “있는 그대로” 저장하지 않는다. 정해진 비트 수 안에서 부호, 지수, 가수를 나눠 담고, 그 안에서 표현 가능한 가장 가까운 값으로 반올림해 저장한다.

즉, 문제의 핵심은 두 가지다.

  1. 모든 10진 소수를 2진수로 정확히 표현할 수 없다.
  2. 저장 가능한 비트 수가 유한하다.

1. 10진 소수는 2진수에서 무한 반복될 수 있다

우리가 익숙한 0.1은 10진수에서는 간단하지만, 2진수에서는 무한 반복 소수가 된다.

1
0.1 = 0.00011001100110011...

이 값을 메모리에 그대로 다 저장할 수는 없다. 결국 어느 지점에서 끊고 근사값을 저장하게 된다.

이 때문에 0.1, 0.2, 0.3 모두 사람이 기대하는 값과 완전히 일치하지 않는 내부 표현을 가진다.

2. 정밀도에는 한계가 있다

대표적으로 IEEE 754 표준의 floatdouble은 각각 정해진 비트 수를 가진다.

타입가수부 비트대략적인 10진 정밀도
float23비트약 7자리
double52비트약 15~16자리

즉, 숫자를 아무리 정교하게 계산하더라도 표현 가능한 비트 수를 넘는 순간 오차가 발생한다.

그래서 어떤 문제가 생기나

가장 대표적인 문제는 세 가지다.

비교 연산이 기대대로 동작하지 않는다

1
2
double a = 0.1 + 0.2;
System.out.println(a == 0.3); // false

이 경우 a0.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진 계산이 필요한가

부동소수점은 빠르고 범용적이지만, 정확성을 보장하는 도구는 아니다. 숫자의 의미가 중요한 도메인일수록 이 차이를 명확히 이해하고 타입을 선택해야 한다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

댓글

아직 댓글이 없습니다