48.5 실수 값의 오차

파이썬에서 0.1 + 0.2의 값은 얼마일까요? 0.3이 나올 것 같지만 실제로는 0.30000000000000004가 나옵니다. 파이썬은 실수를 부동소수점 방식으로 표현하는데 부동소수점은 실수를 정확히 표현할 수 없는 문제가 있습니다. 그래서 정확히 0.3이 아닌 0.30000000000000004가 나옵니다.

>>> 0.1 + 0.2
0.30000000000000004

예를 들어 1부터 10까지 정수는 10개지만 실수는 무한히 많습니다. 컴퓨터에서는 숫자를 유한개의 비트로 표현하는데 실수는 유한개의 비트로는 표현할 수가 없습니다. 따라서 실수는 유한개의 비트를 사용하여 근삿값으로 표현합니다. 즉, 0.300000000000000040.3을 표현한 근삿값입니다.

만약 두 실수가 같은지 판단할 때는 ==를 사용하면 안 됩니다. 다음과 같이 0.1 + 0.20.3은 같지 않다고 나옵니다.

>>> 0.1 + 0.2 == 0.3
False

0.1 + 0.20.30000000000000004이므로 0.3과는 다릅니다. 이렇게 실수를 근삿값으로 표현하면서 발생하는 문제를 부동소수점 반올림 오차(rounding error)라고 합니다. 따라서 실수를 비교할 때는 연산한 값과 비교할 값의 차이를 구한 뒤 sys.float_info.epsilon보다 작거나 같은지 판단해야 합니다.

>>> import math, sys
>>> x = 0.1 + 0.2
>>> math.fabs(x - 0.3) <= sys.float_info.epsilon
True

x의 값 0.30000000000000004에서 0.3을 뺀 값이 sys.float_info.epsilon보다 작거나 같으므로 두 값은 같은 값이라 할 수 있습니다. 특히 math.fabs를 사용하여 두 값의 차이를 절댓값으로 만들면 음수가 나오더라도 정상적으로 판단할 수 있습니다.

여기서 sys.float_info.epsilon에 저장된 값을 머신 엡실론(machine epsilon)이라고 부릅니다. 어떤 실수를 가장 가까운 부동소수점 실수로 반올림하였을 때 상대 오차는 항상 머신 엡실론 이하입니다. 즉, 머신 엡실론은 반올림 오차의 상한값이며 연산한 값과 비교할 값의 차이가 머신 엡실론보다 작거나 같다면 두 실수는 같은 값이라 할 수 있습니다.

파이썬 3.5이상부터는 두 실수가 같은지 판단할 때 math.isclose 함수를 사용하면 됩니다.

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True

반올림 오차가 없는 고정소수점을 사용하려면 decimal 모듈의 Decimal을 사용하면 됩니다.

>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.2')
Decimal('0.3')

순환소수는 고정소수점이라도 정확히 표현할 수 없습니다. 이때는 fractions 모듈의 Fraction을 사용하여 분수로 표현하면 됩니다.

>>> from fractions import Fraction
>>> Fraction('10/3')    # 10을 3으로 나누면 순환소수 0.33333...이지만 분수 3분의 10으로 표현
Fraction(10, 3)