이산수학

실수를 표현하기 위한 IEEE 754 방식의 부동소수점

느억맘 2021. 4. 6. 00:20

데이터의 표현법이 또 있다고???

 

 

 

앞서 다뤘던것중에서 빠진것이 하나 있다. 바로 "실수"

 

실수는 뭐 학창시절에 배웠던 것처럼 정수 사이에있는 무수히 많이 표현할 수 있는 수들이다.

 

정수 0과 1 사이에는 무수히 많은 실수들이 존재한다. 거의 무한대라고 볼 수 있음.

 

소숫점을 이용해 표현할 수도 있고 분수를 이용해 표현할 수도 있는데 소수점을 이용해 많이 표현하는 것 같다.

 

자릿수 표현역시 2^-1 2^-2 .... 등등으로 표현함.

 

그럼 소수점 역시 2진수로 표현하는 법을 알아야겠지?

 

보통 10진수에서 2진수로 표현할때 정수같은 경우는 나눗셈을 한 나머지를 이용해 구했는데

 

실수의 경우 2씩 곱해서 나온 정수부분을 이용해 구하는데 거의 메커니즘이 비슷하다고 볼수 있다.

 

 

오 아주 간편한 방법이잖아?

 

 

하지만 이 방법으로 10진수를 2진수로 변환하는데 애매한 수들이 존재한다.

 

예를 들자면 10진수 "0.1"이 있다.

 

이 0.1 을 위의 방법을 이용하여 2진수로 변환하려하면 0.000110011001...... 등으로 딱 떨어지지가 않는다.

 

즉, 무리수가 되어버린다는것임;;; 말그대로 2진수로 정확하게 표현하지 못한다는뜻임.

 

 

 

아니 그럼 저걸 컴퓨터에 어떻게 저장하는데???

 

 

 

뭐, 역사적으로 여러가지 방법들이 제시되었겠지 당연히.

 

그 중에서 주로 많이 알고있는 방법이 바로 "고정 소수점 수" 방식과 "부동 소수점 수" (IEEE 754 표준) 방식이 있음.

 

먼저 고정 소수점 수에 대해 알아볼건데 말 그대로 소수점을 고정하는 방식임.

 

예를 들어 Q15.16 이면 정수부분은 15 비트만큼 실수부분은 16비트만큼 공간을 할단한다는 뜻인데

 

뭐 어짜피 잘 쓰지도않으니 자세히 설명하지는 않고 장단점만 말하고 넘어감

 

보통 고정 소수점 수의 장점이 부동 소수점 수의 단점이 되고, 단점역시 그 반대가 됨

 

이 고정 소수점 수 같은경우 표현할 수 있는 범위의 값은 확실하게 표현 가능하다만...(오차가 발생하지 않음)

 

표현 가능한 범위가 너무 작고 속도가 부동 소수점 수 대비 느림

 

무엇보다도 현재 이 방식은 거~의 대부분 사용하지 않음.

 

왜냐? 일단 요즘 CPU에 있는 실수 계산 전담 장치가 이 고정 소수점 수를 지원하지 않기때문인 이유가 가장크다.

 

 

 

아니 부동 소수점 수 방식이 대체 뭐길래?

 

 

앞서 말한 고정 소수점 수 방식이 임의의 위치에 소수점 표시를 고정한다면

 

부동 소수점 수(floating-point number 영어도 말그대로 둥둥떠다니는 점) 는 이역시 말그대로 소수점 표시가 자유자재로 움직일 수 있음을 의미한다.

 

그니까 고정소수점이랑 다르게 지맘대로 소수점이 앞으로 갔다 뒤로갔다 마음대로 할수 있다는것.

 

이로인에 한번 소수점 위치를 정하면 정수부분과 실수부분의 크기에 제약을 받는 고정소수점 방식과 다르게

 

정수부분과 실수부분이 어떻게 오든지간에 발생되는 문제가 사라진다는 장점이 있음.

 

부동소수점 방식은 IEEE 754 표준을 따르는데 최근의 CPU는 물론 GPU까지 거의다 이 방식을 지원한다.

 

(대표적인 자료형들 : float(32bit) single(32bit) double(64bit) half(16bit 이건 거의 GPU에서만 사용) )

 

그리고 부동소수점 방식은 고정소수점 방식과 다르게 오차가 존재한다.(이걸 정밀도가 떨어진다고 함)

 

단점이라고 할 수 있을듯. 은행 계좌와 같이 매우 정확한 값을 요구하는 부분에서는 사용이 불가능한데

 

무조건 오차가 발생하는게 아니라 특정 수에서만 그러므로 오차가 발생할 수도 안할수도 있다는걸 상기해야함.

 

애초에 연산속도가 매우 빨라서 살짝 오차가 있어도 티안나는 것들 (예를 들자면 게임에서의 파티클 효과 등등)

 

등에 매우 많이사용된다.

 

그리고 과학적 표기법이라고 너무 긴 실수는 한눈에 보기 싫으므로 간략하게 표기하자.

 

ex) 110.011 => 110011 x 2^-3

 

100000 => 1 x 2^5

 

식으로 표현하면 [m x 2^n] 여기서 m은 significand, fraction, mantissa(중요하다는 뜻 즉 중요한 수?) n은 지수부분임

 

m을 가수라고도 하는데 왜 가수인지 전혀 알 수가 없음. 미스테리 그자체 어쨋든 가수라고 외우자(?)

 

m은 반드시 존재해야 하는 수이고(유효숫자라고도 함) n은 0같이 없어도 되는 수들

 

즉, 110.011에서의 유효숫자는 110011이고

 

100000에서의 유효숫자는 1인것.

 

 

생각해보니 정확도란 표현도 있는데 정확도정밀도랑 무슨 차이임?

 

 

정확도(accuracy)와 정밀도(precision)의 차이라.....

 

이사진만 보면 이해함

위의 사진만 봐도 충분히 이해할것이다.

 

그리고 과학적 표기법중 정규화한 과학적 표기법이라고 있는데 뭐냐면,

 

위에서 110.011 을 110011 x 2^-3 이라고 표현했는데 

 

1.10011 x 2^2 이렇게 왼쪽 가수부분이 항상 1이 나오게 표현하는 방식임.(생각좀 해보면 0이아닌 1인 이유를 알 수 있음 애초에 0이 유효숫자가 될수 없으니까)

 

100000 역시 1 x 2^5 이런 식으로 깔끔하게 표현 가능하다.

 

앞으로 이러한 방식으로 표현할 것이기에 꼭 잊으면 안된다.

 

 

아니 그래서 부동소수점수는 어떻게 표현하는데?

 

 

IEEE 754 표준을 따르는 부동소수점수......... 그 표준을 기준으로 아래 사진과 같이 표현한다.

 

IEEE 754를 표준으로 하는 부동소수점 표현 방식

 

그림에서 보다시피 맨 왼쪽 비트는 부호비트로 사용 그 뒤 8비트는 지수비트 그외에는 가수비트로 사용한다.

 

여기서 가장 중요하게 생각해야할점은 일반적으로 정수같은 경우엔 앞서 배운것처럼 음수는 2의 보수를 사용하여 표현

 

하였지만, 실수의 경우에는 IEEE 754 방식에 의거하여 보수가 아닌 저 왼쪽 부호비트를 통해서만 음,양수를 결정한다.

 

즉 부호비트가 0이면 양수 1이면 음수 이렇게 말이다. 보수를 사용하지 않는다는 것을 헷갈리지 말자

 

(이게 왜그런거냐 하면 부동소수점수와 정수같은 경우 처리를 하는 장치가 다른 것으로 알고있음.)

 

먼저, 지수비트는 8비트 총 2^8 이므로 256개를 표현 가능함.

 

근데 여기서 지수가 양의 정수만 있는게 아닌 음의 정수도 있기 때문에 총 -128 ~ 127 개의 범위를 가져야함.

 

(128 기준 -128 ~ 127, 127 기준 -127 ~ 128)

 

그래서~~~~ 이걸 어캐 표현하냐? 

 

음수, 양수 총 128개로 표현가능하니

 

만약 지수부분이 전부 11111111 즉 255면 여기에 127을 빼면 128이 나온다 즉 실제지수는 2^128

 

마찬가지로 전부 00000000 즉 0이면 127을 뺐을시 -127 즉 2^-127

 

지수가 0인 경우를 표현하고싶다!!!! 간단하게 이 계산과정을 역으로 하면됨. 2^0이라 가정하고

 

여기에 127을 더해주면 됨. 0111 1111 인경우가 되겠음.

 

이 과정은 아주 중요하니 꼭 잊지말자. 127

 

가수부분은 총 23비트를 사용가능하다. 근데 중요한 점이 있음.

 

앞에 정규화한 과학적 표기법이라고 왼쪽에 항상 1이 나오게 즉 1을 기준으로 식을 표현하는건데

 

생각해보면 어짜피 1이 나올거 저 23비트 가수부분에 요 1은 저장하지 않자라고 약속을 한거임..

 

즉 ±1 x (1.b22b21 .... b0) 2^e   (b는 가수, e는 지수부분) 저 1은 암묵적으로 생략을 한다는것을 인지해야함.

 

(비트에 없다고 1이 없다는 생각을 하는 실수를 해서는 안된다 이말)

 

부호비트는 0 지수비트는 1000 0101 가수비트는 1001 1000 1000 0000 0000 000

위의 부동소수점수를 정규화된 과학적 표기법으로 표현해보자.

 

먼저 부호비트는 0이므로 양수

 

지수비트는 1000 0101 10진수로 133 이므로 여기에 -127을 하면 6이된다.

 

(뭐 2진수니 2진수로 빼도 되겠지만 10진수가 가시성이 좋으므로... 걍 취향차이)

 

가수비트는 1001 1000 1000 0000 0000 000 여기에 생략된 1까지 전~부다 더해준다.

 

1 + 2^-1 + 2^-4 + 2^-5 + 2^-9 = 1 + 0.5 + 0.0625 + 0.03125 + 0.001953125 = 1.595703125 가된다.

 

다 합쳐보면 +1 x 1.595703125 x 2^6 이 된다!!

 

부동소수점수로 표시하는건 위 방법의 역순.

 

 

--------------------------------------------------------------

 

 

위에서 지수의 표현범위는 0 ~ 255 즉 127기준 -127 ~ 128 이라 했는데 실제로는 -126 ~ 127의 범위를 갖는다.

 

그 이유는, 0 과 255 즉 최솟값과 최대값은 IEEE754에 의거한 각각의 역할이 따로 존재하기 때문이다.

 

먼저, 0 즉 -128은 기존 표준식으로 표현할 수가 없다. (0이지만 실제로는 0.0 왜? 정수가 아닌 부동소수점이기 때문)

 

가운데 시그마 있는 공식이 표준식인데 지수부분(공식에서의 지수) 은 0으로 표현할 수 없으므로 일단 제외하고

 

저 시그마 부분이 -1이 되어야 0이 되는데 결론적으로 -1이 나오지가 않는다.

 

그래서 지수가  -127 인 경우일때의 전용 공식을 하나 새로 만들어냈음.

 

저 공식같은경우 가수부분이 0이 될 수 있을뿐만 아니라, 기존 표준식과 틀리게 0에 수렴하는 매우 작은

 

부동소수점을 표현할때도 사용한다. (비정규화 수 라고함)

 

그리고 최대값 같은 경우에는 무한대와 NaN(Not a Number) 를 표현하기 위해 따로 빼놓은 것이다.

 

예를들어, 지수가 128 이고 가수가 0일때 무한대라고 하고 0이아닌 그 이외의 값일때는

 

NaN이라 표현한다.

 

(보통 무한대는 분모가 0에 수렴할때를 의미하는데 0.0 이라 표현함. 0.0은 0이 아니라 0에 아주 가까운 수라는 뜻의 컴퓨터 용어, NaN이란, 분자분모가 둘다 0이거나 무한대인 경우는 표현이 불가능)

 

 

32비트 부동소수점 예시)

 

10진수 3.75를 부동소수점으로 표현하기

 

3 → 11(2)

 

0.75 → 0.75 x 2 = 1.5, 0.5 x 2 = 1.0  → 11(2)

 

=> 11.11(2)

 

정규화된 표기법으로 변환 → 1.111 x 2^1

 

부호 비트 → 0

 

지수 비트 → 지수가 1이므로 1 + 127 = 128 128의 2진수는 1000 0000

 

가수 비트 → 1은 기본 생략이므로 뒤의 111만 표시 => 1110 0000 0000 0000 0000 000 총 23비트

 

→ 0 1000 0000 1110 0000 0000 0000 0000 000 총 32비트

 

4비트 단위로 다시 쪼갤시 0100 0000 0111 0000 0000 0000 0000 0000 총 32비트

 

이 예시같은경우엔 가수부분이 2진수로 딱 맞아떨어져서 쉬운편인데

 

딱 안맞아떨어지는 경우도 있음. 이러한 부분에서 오차가 발생한다는 것임.

 

부동소수점 범위인데 주황색부분이 표준 공식으로 표현할 수 있는범위 파란색 부분이 지수가 -127일 때의 특수 공식으로 표현할 수 있는 범위 0에 좀더 가까운 범위를 표현할 수가 있음.

 

32비트 부동소수점의 정밀도? 

 

 

앞서 부동소수점은 오차가 발생할 수밖에 없다고 했는데 그럼 정밀도는 얼마나 될까?

 

IEEE 754에서 이렇게 정의를 하였는데 뭐냐면 

 

10진수 실수를 float에 저장했다가 다시 그걸 10진수 실수로 변환할때 원래 값이 그대로 나올수 있게 하는 최대

 

유효숫자는 6개다.

 

라고 하는데 이게 무슨말이냐면 

 

 

자동으로 6자리까지만 나옴(?) 어쨋든 정밀도 6까지만 보장

위의 사진에서 보다시피, 10진수 123456을 실수로 변환시 위와같이 6자리까지는 정상적으로 잘 나오는 반면,

 

그 이상의 수는 이상한 값이 나오는 것을 알수 있음. 

 

아니 그런데 123456이 아니라 123455 잖아?

 

그래서 IEEE 754에서 정의한 저 노란색 친 문구의 의미에 제시된 유효자리수 그다음 자리수에서 반올림하라는 조건이 또

 

있다. (유효자리수가 2이면 3자리에서 반올림, 5면 6에서 반올림 등등.. 여기선 유효자리수가 6이므로 7에서 반올림함)

 

반올림을 하게되면 123456이 정상적으로 나오는 것을 알수가 있다.

 

이로써 최대 6자리까지는 정밀도를 보장한다는 것을 알수가있음.

 

(여기서 유효숫자 6자리인 수들 → 123.456 ,12345.6, 555555, 0.000434234, 555543)

 

그래서 float 즉 32비트 부동소수점을 사용하기 위해서는 항상 유효숫자에 대해 생각을 해보고 사용해야 한다.

 

(double 64비트를 쓰면 좀 나아지겠지만은 메모리 면이나 속도면이나 손해보는게 많기때문에 보통 float 많이사용)

 

정밀도 9에 대한 이야기는 귀찮아서 안씀 뭐 6에비해 중요성이 떨어지기도 하고 우움..머리 터질거같아서

(대충 요약하자면, 두 부동소수점 수가 다른지 확인하기 위해서 확인해야할 유효 숫자의 개수가 최소 9자리수 즉, 9자리수 까지는 확인해야한다는 뜻)

 

 

그 이외의 자료형 변환과 부동소수점 비교에 대해

 

 

보통 int 형은 32비트를 사용하고 float 형 역시 32비트를 사용한다.

 

만약에, 32비트를 모두 사용하는 int 형을 그대로 float 형에 넣을시, 자료 손실이 발생한다. 왜?

 

float형이 32비트라고 하지만 가수부분은 23바이트만 저장가능 즉 32비트를 모두 수용할 수 없기 때문이다.

 

 

부동소수점끼리는 == 연산을 적용할 수 없다. 왜냐? 표면적으로는 똑같은 3.14 라고 하더라도 실제로는 

 

3.14xxxxxxxxxxxxxx.... 이기 때문에 똑같다고 보기 힘들기 때문이다.

 

ex) 정수 3과 3은 3 == 3 이 말이 되지만 부동소수점인 3.14 == 3.14 는 성립하기 힘들다는 뜻.

 

이 문제를 해결할 가장 좋은 방법은 되도록이면 부동소수점을 서로 비교하지 않는 것이겠지만,

 

해야만 하는 상활일때는 비교연산자와 엡실론이라는 것을 이용해야 한다.

 

(엡실론 : 엄청나게 작은 양수  프로그래밍 언어에 따라 정의된 엡실론의 크기가 다름)

 

즉, 두 부동소수점을 절대값으로 만든뒤, 그 둘을 뺀 값이 엡실론보다 작다고 하면 거의 같은값이나 다름없으므로

 

이렇게 비교하는것. 근데 가급적이면 부동소수점끼리는 비교하지 않는것이....

 

 

그외에 32비트 부동소수점 말고 16비트, 64비트 부동소수점 방식들이 있는데

 

간단히 설명하자면, 64비트 같은 경우 지수부분과 가수부분을 더 많이 저장할 수 있는만큼 32비트에 비해

 

정밀도가 훨씬 높지만, 그만큼 속도가 매우 느리다.

 

16비트는 그와 반대로 정밀도는 매우 낮지만, 속도가 매우 빠름. 주로 정밀도가 중요하지 않은

 

GPU(그래픽카드)등에 많이 사용되는 방식임.

 

정밀도 : 16 < 32 < 64

속도 : 16 > 32 > 64

(CPU에서는 32, 64비트만 지원)

 

 

부동소수점끼리도 덧셈, 뺄셈, 곱셈, 나눗셈 등이 가능한데 뭐 정수로 하는 방식과 똑같기 때문에

 

한가지 알아야 한다는 점은 계산 결과가 기존 부동소수점의 저장 범위를 초과하면 오차가 발생할 수 있다는것.

 

이거 빼면은 일반적인 계산법과 똑같다.

'이산수학' 카테고리의 다른 글

수학적 귀납법과 재귀, 분할 정복  (0) 2021.05.12
비트 마스킹  (0) 2021.05.11