나는 이렇게 학습한다/Language

Python _ Decimal 모듈로 부동 소수점 문제 해결하기

daco2020 2023. 5. 16. 13:43
반응형

 

Decimal 이란?

 

Decimal은 '부동 소수점 문제'를 해결하고 소수점을 정확하게 표현하기 위해 사용하는 Python 자료형이다.

 

컴퓨터에서는 소수를 이진수로 표현하다 보니, 0.1과 같은 값이 정확하게 표현되지 않을 수 있다. 이러한 문제 때문에 금융, 회계 등 정확한 계산이 필요한 경우에는 Decimal을 사용해야 한다. 

예를 들어, 파이썬에서 소수형을 그대로 사용하면 다음과 같은 부동 소수점 문제가 발생할 수 있다.

value = 0.1 + 0.2
print(value)  # 출력: 0.30000000000000004


하지만 아래처럼 Decimal 모듈을 사용하면 이런 문제를 피할 수 있다.

from decimal import Decimal

value = Decimal('0.1') + Decimal('0.2')
print(value)  # 출력: 0.3


Decimal은 소수점 값이 정확히 0.3으로 출력된다. 특히 금액 계산과 같이 정확성이 중요한 경우에는 이렇게 Decimal을 사용하면 정확한 계산 결과를 얻을 수 있다.

 

 


 

 

Decimal 사용 팁


1. Decimal 은 소수점 아래의 정밀도를 지정할 수 있다.

 

정밀도는 Decimal이 소수점 아래 몇 자리까지 보여줄 것인지를 의미한다. 아래 코드처럼 getcontext를 이용해 설정할 수 있다. 

 

from decimal import Decimal, getcontext

getcontext().prec = 6  # 소수점 아래 6자리까지 정밀도 설정
print(Decimal(1) / Decimal(7))  # 출력: 0.142857

 

 

 


2. Decimal 을 사용할 때는 항상 문자열 혹은 정수를 통해 생성해야 한다.

 

float형의 소수를 그대로 사용해 Decimal을 생성하면 부동 소수점 문제가 그대로 발생하므로 이를 조심하자.

 

from decimal import Decimal

# 좋은 예
Decimal('0.1') # 출력: Decimal('0.1')

# 나쁜 예
Decimal(0.1) # 출력: Decimal('0.1000000000000000055511151231257827021181583404541015625')

 

 

 

 

3. Decimal 은 상대적으로 연산 속도가 느리다.

 

일반적인 계산에서 Decimal 을 사용한 고정 소수점은 float 의 부동 소수점 연산보다 느리다.

 

아래는 float 과 Decimal 의 연산을 각각 백만 번씩 수행하여 걸린 시간을 나타낸다. 실행환경이나 조건에 따라 차이는 있겠지만, 아래 예시에서는 약 30배 차이가 나는 것을 확인할 수 있다.

 

import timeit
import decimal

# float 연산
float_time = timeit.timeit('a = 3.1415 * 2.7182', number=1000000)
print(float_time) # 출력: 0.010199374984949827

# Decimal 연산
decimal_time = timeit.timeit('a = decimal.Decimal("3.1415") * decimal.Decimal("2.7182")', setup='import decimal', number=1000000)
print(decimal_time) # 출력: 0.3017380420351401

 

 

 

 

4. Decimal 과 int 는 서로 계산할 수 있다.

 

Python에서는 Decimal 과 int 의 연산을 처리해 준다. 하지만, 결과는 int 타입이 아닌 Decimal 타입이다. 그러므로 정수가 아닌 소수점 아래 값이 생길 수 있다.

 

from decimal import Decimal

d = Decimal('10.5')
i = 2

print(d + i)  # 출력: 12.5
print(d - i)  # 출력: 8.5
print(d * i)  # 출력: 21.0
print(d / i)  # 출력: 5.25

 

 

 

 

5. Decimal 과 float 는 서로 계산할 수 없다.

 

이와 달리 float 는 Decimal 과 서로 계산할 수 없는데, 왜냐하면 Decimal 과 float 는 각각 다른 방식으로 숫자를 표현하기 때문이다. float 는 근사치를 사용해서 숫자를 표현하지만, Decimal 은 정확한 값을 사용한다. 

그래서 Decimal 과 float 를 함께 계산하려고 하면, Python 은 TypeError 를 발생시킨다. 

 

from decimal import Decimal

d = Decimal('10.5')
f = 2.2

print(d + f)  # TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'

 

 


 

 

 

추가 설명

'고정 소수점(Fixed Point)'과 '부동 소수점(Floating Point)'

 

먼저 '고정 소수점'은 이름에서 알 수 있듯이 소수점의 위치가 고정되어 있다. 예를 들어 123.45 같은 경우, 소수점은 항상 정수 부분과 소수 부분 사이에 위치한다. 고정 소수점은 오차 없이 정확한 값을 얻을 수 있다. 하지만 표현할 수 있는 값의 범위가 제한적이라는 단점이 있다. 예를 들어 32비트 시스템에서는 최대 약 2억 정도의 수만 표현할 수 있다.

반면에 '부동 소수점'은 소수점의 위치가 고정되어 있지 않고 움직일 수 있다. 예를 들어, 123.45는 1.2345 x 10^2로 표현할 수 있다. 여기서 1.2345는 가수(mantissa)이고, 2는 지수(exponent)다. 소수점의 위치는 지수에 의해 결정되므로, 부동 소수점 방식을 사용하면 매우 크거나 작은 숫자를 효율적으로 표현할 수 있다.

 

하지만 부동 소수점 방식의 가장 큰 단점은 오차가 발생할 수 있다는 점이다. 이는 컴퓨터가 2진수를 사용하기 때문에 발생하는데, 일부 10진수 소수는 2진수로 정확히 표현이 불가능하기 때문이다.

 

*부동 소수점에 대해 좀 더 자세히 알고 싶다면 부동 소수점 위키백과를 참고하라.

 

 

 


 

 

마치며


고정 소수점과 부동 소수점은 각각 장단점이 있다. 그러니 어떤 상황에 어떤 소수점 표현 방식을 사용할지 주의를 기울여야 한다.

 

고정 소수점은 정확도가 중요한 계산에 적합하지만 연산 속도는 느릴 수 있다. 부동 소수점은 연산 속도가 빠르고 큰 범위의 수를 다루는 데에 적합하지만 정확성이 떨어진다.

 

이 두 가지 방식의 차이를 이해하고 상황에 맞게 적절히 활용해 보자.

 

 

 

 

 

 

반응형