온백의 코딩 블로그

온백의 비밀 기록방

Diary/코딩 잡 지식

"float(실수) 타입의 저장 방식"에 대해 알아보자! - '0.1 + 0.2 == 0.3이 틀렸다고?'

온백 hundred_100 2023. 10. 21. 20:54

한동안 코딩을 접고 있다가 다시 시작하려고 하니,

여러 수학 공식들이 나의 머리를 아프게 한다.

어려워서 아픈 게 아니라 오랜만에 수학 머리를 쓰려하니 뇌에서 거부하는 느낌..

 

아무튼, 그렇게 코딩을 놓고 있다 보니 float타입의 저장 방식을 잊어버리고 있었다.

그래서 다시 찾아보고, 기억이 나서 정리하려고 한다.

 


 

소수점 뒷자리를 이진수로 바꾸려면?

 

정수 부분을 이진수로 변환시키는 방법은 다 알 것이다.

그렇다면 소수 부분은 어떻게 변환시킬까?

 

정수는 소수점 뒷자리가 없어 계속 2로 나누면서 나머지를 체크하면 된다.

하지만 소수를 그렇게 한다면.. 끝이 없다.

 

'그래서 실수는 2를 계속 곱해준다!'

2를 곱하다가 정수 부분이 1이 되면 그걸 체크하면 된다.

이 과정을 0이 될 때까지 반복한다.

 

이해를 돕기 위한 그림...인데 1로 곱해지는 게 마지막 밖에 없ㄴ..?

 

하지만 모든 것이 1.0으로 끝나진 않는다..

 

예를 들어, 0.3을 계속 곱한다면..

 

0.3 x 2 = 0.6 --- 0
0.6 x 2 = 1.2 --- 1
(1.2 - 1.0)
0.2 x 2 = 0.4 --- 0
0.4 x 2 = 0.8 --- 0
0.8 x 2 = 1.6 --- 1
(1.6 - 1.0)
0.6 x 2 = 1.2 --- 1
.
.
.
.

 

이런 끝이 안나는 수를 무한 소수라고 하며,

저장 공간은 한정되어 있기 때문에, 뒤에 수들은 전부 삭제시켜 버린다.

(그리고 그만큼의 오차가 생긴다)

 

이 숫자들이 0.1 + 0.2가 0.3이 아니라는 이상한 결과를 내놓는 이유이기도 하다.(그러니 정확한 결과가 필요하다면 꼭 정수로 바꿔서 사용하자)

 

부동소수점 저장 방식

 

소수점 뒷자리 계산 방법을 알았으니 본론으로 돌아가보도록 하겠다.

 

'그냥 정수와 소수 부분을 따로 만들어버리면 되는 거 아닌가?'라고 생각할 수 있다.

하지만, '그냥' 나눠버리면 문제가 생긴다.

 

부호 비트 1bit, 정수부 15bit, 소수부 16bit로 나누는 것을 '고정소수점 방식'이라고 한다.

앞에 부호비트는 +(0), -(1)을 저장하고,

정수부와 소수부는 말 그대로 정수 부분과 소수 부분을 저장한다.

 

부호 비트 정수부 소수부
1bit 15bit 16bit

 

그런데 만약 소수를 이진수로 변환했는데 그 값이 길다면?

아니면 정수 부분이 너무 크다면?

저장 범위를 넘어버려 정확한 값을 넣을 수가 없다.

 

그래서 조금 더 정확한 수를 저장하도록 '부동소수점 방식'을 사용한다.

(IEEE 754 표준이다.)

 

'부동소수점(floating point)'은 소수점이 말 그대로 "floating"하는데,

이진수로 변환한 실수를 정수부가 한자리가 되도록 하여 저장시킨다.

 

예를 들면,

 

1.(정수 부분 + 소수 부분) x 2^n

(ex)

1001.110 -> 1.001110 x 2^3
10.1101 -> 1.01101 x 2^1

(이진수이기 때문에 2의 n제곱을 곱해준다.)

 

앞자리 '1' 빼곤(정수부) 전부 소수 부분(소수부)으로 이동시키고, 지수(n)를 저장하는 것이다.

 

그러면 굳이 정수를 저장할 이유가 없다! (어차피 1이다.)

 

그래서 정수부가 지수부가 되며, 소수부가 가수부로 된다.

지수부에 지수 n을 저장하는 것이다.

(지수부는 크기가 작아져도 된다. 수가 작기 때문이다.)

 

부호 비트 지수부 가수부
1bit 8bit 23bit

 

이렇게 끝이었으면 좋겠지만, 여기서 또 문제가 생긴다.

'실수가 0보다 작으면?'

 

부동소수점은 앞자리가 꼭 1이어야 하기 때문에,

0보다 작은 수를 저장하기 위해선 2^(-n)을 해야 할 수도 있다!

 

이 문제를 해결하기 위해 지수에 '바이어스 수'를 더하여 저장한다.'바이어스 수'는 127인데, 지수부가 127보다 작으면 음수, 크면 양수라고 지수의 부호를 구분한다.

 

(ex)

-1001.0110 -> -1.0010110 * 2^3

지수 비트: 1 (-)
지수부: 3 + 바이어스 수(127) = 130 -> 10000100
가수부: 0010110

저장: [1][10000100][0010110]

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

0.0110 -> 1.110 * 2^(-2)

지수 비트: 0 (+)
지수부: -2 + 바이어스 수(127) = 125 -> 01111101
가수부: 110

저장: [0][01111101][110]



(생략해서 그렇지, 가수부에 남은 비트들은 전부 '0'으로 채워집니다!)

 


 

2023.07.17 - [Note/알고리즘] - "이중 연결리스트(Doubly Linked List)" 알고리즘 짜보기! - 초보자도 쉽게!

 

 

2023.05.28 - [Note/알아두면 좋은 포털 사이트 정보들] - Gmail "확인에 사용할 수 없는 전화번호 입니다" 해결법

 

2023.05.29 - [Note/C language] - [C, C++, C#] "bool"타입은 뭘까? - "boolean"에 대해 간단하게 알아보기

반응형