IEEE 754와 float point형에 대해

지환·2022년 3월 17일
0

정리해두기 위해 쓰는 글..


IEEE 754는 아래 링크에 잘 설명돼있으니 보면 된다.
(괄호 안은 같은 링크)
1: http://studyfoss.egloos.com/4956717
(http://egloos.zum.com/studyfoss/v/4956717)
2: http://egloos.zum.com/studyfoss/v/4960394
(http://studyfoss.egloos.com/4960394)

여기도 잘 설명돼있네.
1: http://www.secmem.org/blog/2020/05/15/float/
2: http://www.secmem.org/blog/2020/05/15/float/
둘다 보면 좋지 싶다.


2진수 ↔ 10진수 변환

  1. 소숫점 윗자리
    A) 2진수 → 10진수
    : 각 자리에 2^n을 곱해주면 된다.
    B) 10진수 → 2진수
    : 10진수의 모습을 한 어떤 숫자도 같은 값을 가지는 2진수 모습이 있을 것이다.(ex. 3 == 11)
    그런 모습이라고 생각(상상)하며, 10진수를 2로 나누며 나머지를 역순으로 적어주면 된다.
    2진수를 2로 나누면 마지막 자리가 소숫점 아래로 밀리며 나머지로 나오게 되는 꼴이다.

  2. 소숫점 아래자리
    A) 2진수 → 10진수
    : 각 자리에 2^(-n)을 곱해주면 된다.
    B) 10진수 → 2진수
    : 위와 마찬가지로 2진수 형태를 생각(상상)하며 10진수 형태의 숫자에 2를 곱하며 소숫점 위로 올라오는 숫자를 차례대로 적어주면 된다.

2진수 형태를 생각(상상)하라는건 저렇게 연산하는 이유를 쉽게 이해하기 위해서다.

각 계산방식은 다른 곳에도 적용 가능하다.
예를들어 소숫점 윗자리 10진수를 2진수로 바꿀때,
'2진수로 변환한 각 자리 수'에 '2진수로 변환한 10^n'을 곱해서 구해도 된다.
하지만 그건 복잡하니까 다른 방식으로 하는 것이다.
(각 자리수를 2진수 형태로 바꿔야되는 이유는 1-A에서도 사실상 10진수로 변환된 형태로 각 자리수를 보기 때문이다.)

10진수 → 2진수로 빨리 바꾸기

ex) 100을 2진수로 바꿔보자.
2의 거듭제곱 수들 중 64가 100 이하에서 가장 근접한다.
그래서 일단 1000000(2)1000000_{(2)} 으로 한다.
100-64는 36이니까, 36 이하 2 거듭제곱 수들 중 가장 근접한 수는 32이다.
그래서 1100000(2)1100000_{(2)}이 된다.
남은 수는 4이니, 최종적으로 1100100(2)1100100_{(2)} 이다.


IEEE 754의 표현법은 이렇게 이해했는데,
float type의 precision이 잘 이해되지 않았다.
분명 책에선 float의 precision, 정확도가 6이라는데 3.1을 IEEE 754에 따라 저장해보면
3.099999904632568359375란 숫자가 저장된다.
아니 precision이 6이면 적어도 6자리는 정확하게 저장이 돼야되는 것 아닌가?
대체 precision이 무슨 의미지?

https://stackoverflow.com/questions/49961587/why-floating-points-numbers-significant-numbers-is-7-or-6
여기 제일 위 답변을 보면 명쾌하게 얘기해주고있다.

여기서 말하는 precision이란,
"float value 하나씩에 각각 mapping될 수 있는 실수 자릿수가 최대 몇개인가?"
이다.

풀어서 설명하자면,
우리가 아는 실수는 무한하다.
하지만 IEEE754에 따르면 float 값은 32비트로만 표현할 수 있어서, 컴퓨터에서 표현할 수 있는 실수는 유한하다.
float형이 표현할 수 있는 최댓값과 최솟값 사이에 표현 할 수 있는 실수는 총 2^23개 이지만,
실제로 존재하는 실수는 무한하기 때문에 이를 모두 정확하게 표현할 수 없다. (그 이유가 아니더라도 애초에 10진수를 2진수로 바꾸면 표현의 한계가 생긴다. 유한소수가 무한소수가 되는 등..)
즉, 이런 한계 때문에 특정 범위 내의 무한한 실수들은 하나의 float 값으로 표현(mapping)되게 된다. 따라서 1.23456 같은 컴퓨터 상의 float 값만 놓고 봤을 때는 그 값의 원래 값을 구분할 수 없게 된다. 1.2345600000000000001을 저장하거나, 1.23456000000000000000000001도 저장했더니 1.23456이라는 값으로 컴퓨터 상에서 뭉뚱그려 표현될 수 있기 때문이다.(예시라 저 값들이 실제로 1.23456으로 표현되는진 모름)
그래서 precision이란 개념을 도입한 것이다. precision은 "몇자리까지는 정확하게 저장돼! 예를 들어 0.1은 정확하게 0.1로 컴퓨터에 저장돼!" 라는 개념이 아니라(애초에 0.1은 이진수로 변환하면 무한 소수가 돼서 정확하게 저장할 수 없다.), "precision만큼의 자릿수를 가지는 float 숫자 끼리는 서로 구분돼!" 라는 의미에 더 가깝다.
그래서 위 링크를 보면 어떤 범위에선 precision이 6이고 어디선 7이다. float의 최소 precision은 6이다.

Q) 저 링크 글을 보고 들 수 있는 의문이, "아니 뭐 90만 보다 100만이 크니까 당연히 중복되는 mapping이 있을 수 있는건 맞는데, 100만개의 숫자에 800만개의 mapping 가능한 float이 있다고 해서 중복되는 mapping이 없을거란 보장은 어떻게 하지?"
A) float 값들이 2^n ~2^(n+1) 에선 일정한 값 a만큼씩 떨어져있기때문에 그렇게 보는게 가능하지 싶다.. 정확한 수학적 증명은 못하겠네.

Q2) 3.13.0999999는 왜 구분이 안되지?(== 왜 같은 float값으로 mapping이 되지?) precision내에선 구분이 돼야되는거 아닌가?
A) 2^1~2^2 범위에선 precision이 7이다.
해당 범위에서의 precision(7) 이하로 자릿수를 가지는 것들끼리는 모두 구분이 된다.즉, 다른 float value로 mapping이 된다.
따라서 3.1과 3.099999(7)은 구분이 된다.
precision을 넘어가는 자릿수를 가지는 숫자들은, 위에서 말했듯이, 중복 mapping이 될 수 있어서 당연히 다른 숫자와 같은(구분되지않는) float value를 가질 수도 있는 것이다.

Q3) 3.1과 3.0999999이 구분 안되면 해당 float을 print하면 어떤 수가 나오는거지? 이뿐만 아니라 무한한 실수의 특정 부분은 2^32 중 하나의 float에 mapping이 될건데, 그럼 해당 float을 pirnt하면 어떤 숫자인지 알고 print하는거지?
A) 반올림해서 값을 잘라먹지만 않는다면, 실제 저장된 값인 3.099999904632568359375 가 나온다.
이때 적용되는게 FLT_DIG같은 macro이지 싶다..


DECIMAL_DIG, LDBL_DIG에 관해..

얘네는 precision이랑 값은 같을 수 있어도 기본 개념은 다르게 보는게 맞지 싶다.
string → FP → string
FP → string → FP
roundtrip을 해도 같은 값이 나오도록 보장해주는 자릿수를 나타내는 macro..

Q) significant digit(precision)이 6이라고 했을때, 실제 값과 다른 float으로 mapping이 되면, 해당 precision에서 반올림 했을때 같은 값이 나온다는걸 어떻게 보장하나??
A) https://stackoverflow.com/questions/50491808/how-does-float-guarantee-7-digit-precision
수학적 증명이 들어간 완벽한 답은 아니지만, 그래도 어느정도 답변이 되는 내용인듯 하다..(제일 위 답변)

0개의 댓글