수학과의 좌충우돌 프로그래밍

[python] Decimal vs Float, 고정소수점과 부동소수점 본문

프로그래밍 언어/Python

[python] Decimal vs Float, 고정소수점과 부동소수점

ssung.k 2020. 6. 10. 23:15

Float

우선 특별한 작업없이 소수를 사용할 경우 이는 float 타입으로 선언이 됩니다.

하지만 아래 결과를 보면 0.1을 할당한 a는 실제로 0.1이 아닌 많은 소수점을 가지고 있습니다.

왜 이런 문제가 발생하는 것일까요?

a = 0.1
print(type(a))
print(f"{0.1: .30f}")

# <class 'float'>
# 0.100000000000000005551115123126

 

컴퓨터가 실수를 표현하는 방식

아마 다들 아실테지만 컴퓨터는 기본적으로 2진수를 사용합니다.

그렇기 때문에 정수를 표현할 때는 문제가 되지 않지만 소수를 표현할 때는 문제가 발생합니다.

예를 들어 10진수 0.3을 2진수로 표현한다고 하면 0.01001100110011... 다음과 같이 표현이 됩니다.

무한히 0011이 반복되죠.

이러한 문제로 소수를 저장하기 위한 두 가지 방법이 제시되었습니다.

 

부동소수점

우선 위에서 사용한 Float가 사용하는 부동소수점입니다.

부동소수점도 여러 가지 방법이 있지만 일반적으로 IEEE에서 제안한 표준을 따릅니다.

 

이번에도 4byte, 32bit를 기준으로 부호 비트 1bit, 지수 비트 8bit, 가수 비트 23bit를 사용하게 됩니다.

표현하고자 하는 10진수를 우선 2진수로 변환한 다음에 1.xxx * 2^b 꼴로 정규화 합니다.

이 때 b값에 bias(127)를 더해 지수부분에 기록하고, xxx에 해당하는 소수 부분을 가수에 기록하게 됩니다.

쉬운 이해를 위해 22.22를 부동소수점 방법을 통해 변환해보겠습니다.

  1. 22.22를 2진수로 변환10110.001110000101000111101...

  2. 1.xxx * 2^b 의 꼴로 표현

    1.0110001110000101000111101... * 2^4

  1. 각각 기록

    • 양수이므로 부호 비트는 0
    • 4+127을 지수부에 기입 : 10000011
    • 남은 소수 부분을 가수부에 기입 : 0011100001010001111

bias를 왜 더하는걸까요?

위에서 지수부분에 bias를 더해서 기록을 하게 됩니다. 굳이 이러한 과정을 왜 하는 것일까요?

지수에 bias값을 더하는 이유는 음의 지수값을 양수로 표현하기 위해서 입니다. 이를 통해 실수값을 비교할 때 지수의 MSB부터 비교함으로서 빠르게 비교연산을 수행할 수 있습니다.

bias값은 지수의 비트수로 결정됩니다. 지수의 비트수가 n이라고 하면 2^(n-1)-1 이 bias가 되는데 이는 음수와 양수를 절반으로 나눈 값입니다.

 

 

고정소수점

다음은 고정소수점입니다.

고정소수점 방법은 정수와 소수를 표현하는 비트 수를 미리 정하고 이에 맞춰서 표현하는 방법입니다.

실수 표현을 위해 4byte를 사용한다면 32bit이고 이를 적절하게 분배합니다.

예를 들어 부호 비트 1bit, 정수 비트 15bit, 소수 비트 16bit 이런 식으로 말이죠.

하지만 이는 당연히 제약이 따릅니다.

정수 비트를 크게 한다면 소수 비트가 줄어 정밀한 소수를 표현하지 못하게 되고, 소수 비트를 크게 한다면 큰 정수를 표현하지 못하게 됩니다.

고정소수점은 메모리를 많이 사용하면 할수록 데이터를 더 정확하게 표현 할 수 있습니다.

 

 

Decimal

Float 를 사용할 경우 위와 같이 오차가 발생하는 것을 확인할 수 있었습니다. 부동소수점을 사용했기 때문에 어쩔 수 없는 문제죠.

Decimal을 사용하면 이러한 문제를 해결할 수 있습니다.

기본적으로 정수에 대해서도 Decimal을 사용할 수 있습니다.

import decimal

x = decimal.Decimal(15)
print(x)
# 15

 

소수에 대해서는 인자를 string으로 넣어줘야 합니다.

x = decimal.Decimal(0.1)
print(x) 
# 0.1000000000000000055511151231257827021181583404541015625

x = decimal.Decimal("0.1")
print(x)
# 0.1
print(f"{x:.30f}")
# 0.100000000000000000000000000000

 

또한 튜플을 사용해서도 표현할 수 있습니다.

총 3가지 부분으로 나뉘어지는데 가장 앞이 부호, 중간이 값, 마지막이 소수점이 몇자리 인지를 나타내게 됩니다.

x = decimal.Decimal((1, (1, 3, 0, 4, 2), -3))
print(x)  
# -13.042

 

 

마무리

Decimal 이 정확하기 때문에 Float 보다 좋다고 할 수 있을까요?

그렇지 않습니다.

Decimal 은 고정소수점을 사용하며 메모리를 많이 사용함으로서 정확성을 향상시키기 때문에 장단점이 존재하죠.

다음 출력값을 확인하면 어떤 문제가 있는지 확인할 수 있습니다.

import decimal
from sys import getsizeof

x = decimal.Decimal("0.3")
y = 0.3
print(getsizeof(x)) # 104
print(getsizeof(y)) # 24

 

그렇기 때문에 상황에 맞춰서 적절하게 사용하는 것이 중요합니다.

Comments