Logo

파이썬 소수 연산 - float 타입과 decimal 모듈

float 타입

파이썬의 float 기본 타입은 대부분의 다른 프로그래밍 언어들처럼 소수를 내부적으로 이진수의 형태로 저장합니다. (컴퓨터라는 기계는 결국에 0 또는 1로 모든 데이터를 저장히기 때문이죠…) 예를 들어, 십진 소수 0.875을 float 타입으로 저장하면 이진 소수인 0.111의 형태가 됩니다 (1/2 + 1/4 + 1/8).

하지만 불행이도 모든 십진 소수가 이와 같이 딱 떨어지게 이진 소수의 형태로 표현될 수 있는 것은 아닙니다. 예를 들어, 십진 소수 0.895를 이진 소수로 변환해보면 0.111001010001111011...가 되어 무한 소수가 되어버립니다.

즉, 십진 소수 0.895와 매우 가까울 수는 있어도 수학적으로 완벽한 등가를 float 타입으로 표현하는 것은 불가능합니다. 대부분의 십진 소수가 이진 소수로 변환하면 무한 소수가 되는 것을 감안하면, 사실 float 타입으로 저장된 소수는 근사값일 확률이 훨씬 높습니다.

float 연산의 문제점

이와 같이, float 타입이 내부적으로 소수를 저장하는 매커니즘 때문에 float 타입의 소수로 연산을할 때 다음과 같이 어처구니 없는 상황을 맞이할 수 있습니다.

>>> 1.1 + 2.2
3.3000000000000003
>>> 8.95 * 100
894.9999999999999
>>> 0.1 + 0.1 + 0.1 - 0.3
5.551115123125783e-17

이러한 황당한 상황이 발생하는 이유는 float 연산이 보통 사람이 생각하는 십진수 기반 연산이 아닌 컴퓨터 관점의 이진수 기반 연산이기 때문입니다. 그리고 이러한 미세한 연산의 오류는 계산에 있어서 한치의 오차도 용납되지 않는 금융권 시스템이나 재무/회계 관련 소프트웨어에서는 치명적인 버그로 이어질 수 있습니다.

decimal 모듈

정확한 십진수 기반의 연산이 필요한 경우를 위해서 파이썬에서는 decimal 내장 모듈을 제공하고 있습니다. decimal 내장 모듈의 Decimal 클래스를 사용해서 위에서 했던 연산을 반복해보겠습니다.

>>> from decimal import Decimal
>>> Decimal('1.1') + Decimal('2.2')
Decimal('3.3')
>>> Decimal('8.95') * Decimal('100')
Decimal('895.00')
>>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')
Decimal('0.0')

모든 연산이 우리가 예상하던 데로 오차없이 정확하게 이루어지는 것을 알 수 있습니다. Decimal 클래스를 사용할 때 한가지 주의 사항은 생성자에 문자열 대신에 숫자를 넘기면 float 타입을 사용했을 때와 동일한 문제가 발생하게 된다는 것입니다.

>>> Decimal(1.1)
Decimal('1.100000000000000088817841970012523233890533447265625')

따라서 정확한 십진수 연산을 위해서 decimal 모듈을 사용하는 것이라면 Decimal 클래스 생성자에 문자열을 넘기도록 주의바랍니다.

>>> Decimal('1.1')
Decimal('1.1')

마치면서

이상으로 파이썬의 float 연산의 문제점과 decimal 내장 모듈을 이용한 간단한 해결 방법에 대해서 살펴보았습니다. 좀 더 자세한 내용은 아래 파이썬 공식 레퍼런스를 참고바라겠습니다.