소수점이 있는 정수를 비교하다 보면 아래와 같은 결과를 보게 된다.
0.1 + 0.2 == 0.3 # False
처음 보게 된다면 이게 왜 틀리지라는 의문이 들게 된다.
결론부터 말하면 컴퓨터가 숫자를 표현하는 방식의 한계이다.
부동소수점 오차는 왜 생기는가
컴퓨터는 숫자를 삽진법가 아니라 이진법으로 저장한다.
문제는 우리가 사용하는 소수 대부분이 이진법으로는 정확하게 표현되지 않는다는 점이다.
예를 들어 0.1은 십진법으로 바로 표현 가능하지만 이진법으로 바꾸면 무한 소수가 된다.
컴퓨터는 무한한 자릿수를 저장할 수 없으므로 가장 가까운 값을 반올림하여 저장하게 된다.
이때, 생기는 현상이 부동소수점 오차이다.
항상 오차가 발생하지 않는다
float 연산이면 항상 오차가 발생하지 않는다 이진법으로 표현 가능한 값은 오차가 없기 때문에 부동소수점 오차가 발생하지 않는다.
예시
1.0 + 1.0 == 2.0 # True
0.5 + 0.25 == 0.75 # True
0.125 * 8 == 1.0 # True
위 숫자들의 공통점은 전부 2의 거듭제곱 분수라는 점이다.
- 0.5 = 1/2
- 0.25 = 1/4
- 0.125 = 1/8
이런 값들은 이진법에서 정확하게 떨어지므로 오차가 없다.
대표적인 사례
0.1 + 0.2 == 0.3 #False
실제 값을 출력해 보면 정확하게 알 수 있다.
0.1 + 0.2 # 0.30000000000000004
우리가 기대한 0.3이 아니라 소수점 아래에 값이 존재한다.
int와 float 섞어 쓰면 더 위험할까
결론만 말한다면 무조건은 아니다.
아래 사례에서는 문제가 없다.
a = 1 + 1 # int
b = 1.0 + 1.0 # float
a == b # True
2와 2.0은 자료형은 다르지만 값 자체는 정확히 일치하기 때문이다.
아래 사례에서는 문제가 발생한다.
a = 1
b = 0.1 + 0.9
a == b #false
여기서 문제는 자료형이 아니라 0.1, 0.9는 이진법으로 표현 불가능하기 때문이다.
실무에서 사용하는 방법
정밀 계산은 Decimal을 사용한다.
from decimal import Decimal
a = Decimal("0.1") + Decimal("0.2")
b = Decimal("0.3")
a == b # True
Decimal은 십진법을 표현해 주기 때문에 정확하지만 느리고 무겁다.