2019.03.10 TIL
(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)
# 질문에 답하기
실수
- 고정 소수점
고정소수점(Fixed Point) 방식은 소수점 이상, 소수점 이하를 특정 비트로 딱 나눠서 처리하는 것을 말한다.
만약 반반씩 하기로 하면 4바이트 즉 32비트로 처리하면 정수부분은 16비트 소수 부분도 16비트로 표현하는 것이다.
아니면 1바이트는 정수부분(8bit) 3바이트는 소수부분(24bit)로 표현하기도 한다.
하지만 32비트가 표현할수 있는건 한계가 있어 정수부분과 소수부분을 어떻게 나누어 표현하느냐의 문제가 발생한다.
실제 함수를 작성하면서 정수부분은 거의 범위가 적고 소수자리는 길게 필요하다면 소수부분에 비트를 많이 할당할 수 있다.
고정 소수점은 정밀도는 높았으나 표현 범위가 낮다.
- 부동소수점(Float Point)
부동소수점은 정밀도는 낮으나 표현범위 넓다. 따라서 어떻게 정밀도를 핸들링 할 것인가가 관건이다.
위에 고정소수점은 소수점 위치를 정하고 그이상 그 이하 나눠서 처리 했으니
이번엔 소수점 위치가 계속 바뀌는건가 라고 생각하시겠지만 아니다.
컴퓨터 언어에서 쓰이는 실수형 자료형(float이나 double)은 보통 부동소수점 방식을 쓴다. 근데 이 규격은 IEEE에서 정하고 있다. (여기서 쓰이는건 IEEE 754 입니다.)
IEEE 754는 아래 그림과 같은 방식으로 표현됩니다.
1023 —-> 102.3 x 10 / 10.23 x 10 ** 2 / 1.023 x 10 ** 3 / 0.1023 x 10 ** 4 와 같이 다양한 형태로 나타낼 수 있다.
여기서 floating point가 나오는데
floating 은 둥둥 떠다닌다를 뜻한다 이 말은 소수점의 위치가 언제든지 옴겨질 수 있는 것을 말한다.
부동소수점
부동소수점은 단정도 부동소수점과 배정도 부동소수점으로 나누어 진다.
- 단정도 (single precision)
- 32 bit (4byte)
- 부호(1bit) + 지수(8bit) + 가수(23bit)
- 지수부를 넓힌다는 건 표현범위를 넓힌다는 것이다. 가수부를 넓힌다는 건 정밀도를 넓힌다는 것이다.
- 배정도(double precision)
- 64 bit (8byte)
- 부호(1bit) + 지수(11bit) + 가수(52bit) #단정도에 비해 정밀도가 거이 2배이고 표현범위도 늘어남 #단 메모리를 많이 쓰게 됨. 따라서 double을 되도록 안쓰고 싶어한다??
파이썬은 기본적으로 배정도를 사용한다.
import sys
sys.float_info를 해보면
여기서 가수부를 뜻하는 mant_dig을 볼 수 있는데 여기서 53이 되어있는 것을 볼 수 있다.
이는 파이썬이 배정도를 사용한다는 것을 알 수 있음과 동시에 배정도는 가수부가 52bit인데 왜 53bit가 할당되어 있는지에 대한 의문저 역시 품게 된다.
이를 해결하기 위해서는 정규화에 대해 알아야 한다.
5234 -> 523.4 x 10*1
-> 52.34 x 10** 2
-> 5.234 x 10 ** 3
이중에서 무엇을 메모리에 저장할 것인가? 이 수를 정규화시키고 저장한다.
정규화란? 정수 부분이 0이 아닌 1자리 자연수로 만드는 것이다.
110.1(2) —> 1.101 x 2 ** 2
0.0000111 —> 1.11 x 2 ** -5 —> 여기서 1.11을 가수부 2 ** 5는 지수부 2는 기수라고 한다.
따라서 2진수에는 제일 앞이 무조건 0이 아니라 1이 되게 된다.( 1자리 자연수)
+- 1.man x 2 ** —> 여기서 1. 은 멘티사(가수)이지만 따로 저장하지 않는다.(항상 1이 나오므로)
따라서 1.은 따로 저장하지 않으면 더 좋다.
1.010101 x 2 ** 4 일 때 저장은 010101(가수) 이후에 000000000000 으로 나머지 가수부분을 채운다.
따라서 파이썬에서는 man_dig가 1.을 포함하므로 53개로 나타남(왜냐하면 1도 실제로 가수부이므로)
- 이런 일로 인해서 실제 메모리에 저장되는 것과 파이썬에 나타나는 것과 차이가 나타나게 된다.
다음으로 넘어가서 2 ** 5 에서 전체는 지수부라고 표현하고 2는 기수 5는 exponent(제곱) real 이라고 부른다.
여기서 exponent real은 exp - bias로 구할 수 있다.
여기서 bias는 상수인데 bias를 알면 exponent를 구할 수 있다.
따라서 float에서는 지수가 항상 8자리의 비트를 가지므로 bias는 127이다.
Ere = Emem - bias
Emem = Ere + bias —-> 5 + 127 = 132 를 2진수로 바꾼게 Emem 가 된다.
이제 예를 들어 실행해보자
만약에 특정 변수에 10.625라고 변수를 할당해주면 과연 10.625는 어떻게 메모리에 저장될까?
먼저 10.625로 2진수로 바꾸어야 한다. 10.625 = 8 + 2 + 0.5+ 0.125 (현실에서는 이것보다 복잡하는 2진수로 바꾸는게 쉽지 않다)
= 1010.101(2)
이 숫자는 정규화되어 1.010101(2) x 2 ** 3으로 나타난다.
앞에 가수부의 1.을 제외한 010101이 가수부에 저장되고 (가수부는 52자리를 지원하므로 나머지 부분은 모두 0으로 저장)
Ere = Emem - bias 이므로 Emem = Ere + bias 이다.
따라서 지수부를 나타내는 Emem = 3 + 127 이므로 130 이다.
130을 2진법 수로 나타내면 128 + 2 —-> 2 ** 7 + 2 —-> 10000010 이 된다.
따라서 명확하게 적어보면
0(sign) 10000010 (지수부) 01010100000000000—-(가수부 52자리)로 적을 수 있다.
그렇다면 파이썬에서 0.01을 100번 더했을 때 1이 나오지 않는 이유는 무엇일까?
a = 0.01
result = 0.0
for i in range(100):
result += a
result #1.0000000000000007
이는 0.01을 2진법 수로 바꾸었을 때 가수부를 나타내는 자리수가 부족했음을 알 수 있다. 그 숫자의 차이는 미비하나 100번이나 더 해주었을 때 어느정도의 차이를 만들어낸다.
따라서 정밀도에 대한 문제를 부동소수점에서는 꼭 다루어야 하는데 그것의 시작이 바로 옙실론이다.
옙실론은 다음에서 이어서 설명한다!