컴퓨터는 모든 정보를 0과 1로 저장한다.
우리에게 익숙한 10진수로 표현된 정수를 컴퓨터가 저장하는 방법부터 가볍게 보고 실수로 넘어가도록 하자.
컴퓨터는 10진수로 표현 된 수를 0과 1로 저장하기 위해
2진수로 변환한 정보를 저장하게 된다.
23을 예로 들면
이므로
2진수로 표현하면 10111 이다.
(물론 2진수로 변환하는 방법은 다양하다. 23을 2로 나누며 그때의 나머지를 계속해서 저장하는 방법 등. 그러나 뒤에서의 이해를 돕기위해 2의 제곱수로 표현함).
이 10111의 수를 1Byte의 메모리에 0001 0111으로 저장하는 방식이다.
실수를 어떻게 저장하는지 알기전에
실수를 어떤방식으로 표현하는지 알고
순차적으로 접근하는게 좋다고 생각한다.
먼저 실수는
으로 표현 할 수 있다.
고정소수점 방식은 단어 그대로 소수점의 위치를 고정시켜놓고 수를 표현하는 방식이다.
예시) 123.45
123.45는 10진수에서 소수점 밑의 두자리를 고정시켜놓고 표현한 것.
보다시피 "정수.소수" 의 형태로 나타낸다.
이를 고정소수점 방식의 2진수로 표현하면,
정수와 소수가 각각 사용하는 메모리 공간을 정해놓고 사용한다.
부호비트 = 1bit | 정수비트 = 15bit | 소수비트 = 16bit |
---|
예를 든 위의 비트 할당량 대로 굳이 2진수로 변환하면
123.45 = 1111011.1100110011001100... 로
0000 0000 0111 1011 1100 1100 1100 1100 이다.
(2진수도 고정소수점으로 표현할 수 있다는 예시일 뿐 중요하지 않음, 편의상 뒤의 bit들은 버림)
그럼 우리에게 익숙한 고정소수점 방식을 사용하지 않는 이유는 무엇일까?
소수점의 위치를 고정할 때,
정수를 표현하는 bit를 크게하여 고정하면, 큰 숫자는 표현할 수 있지만 정밀한 수치를 표현하기 힘들다.
소수를 표현하는 bit를 크게하여 고정하면, 정밀한 수치를 표현할 수 있지만 큰 숫자를 표한하기 힘들다.
이러한 문제를 해결하기 위해 부동소수점 방식을 사용한다.
고정소수점에서는 소수점 위치가 정해져있었으니, 반대로 정해지지 않았다!
라고 생각하기 쉽지만 그런 표현보다는 (floating - point) 라는 단어에서 알 수 있듯 소수점이 둥둥 떠다니듯이 소수점의 위치가 언제든지 옮겨질 수 있다는게 더 옳은 표현이다.
따라서 부동소수점 방식으로 위의 수를 표현하면
10진수 표현으로는 으로 표현할 수 있고,
2진수 표현으로는 로 표현할 수 있다.
이렇게 부동소수점 방식은 부호(+,-) 가수 * 기수^지수로 구성되어 있다.
이제 왜 부동소수점 방식으로 저장하는지 이유에 대해서는 알았다.
그럼 이제 컴퓨터가 어떻게 저장하는지 알아보자!
부동소수점 저장에 관한 규약은 IEEE 754라는 표준으로 정해져 있고,
IEEE 754에서 (단정밀도 부동소수점인 float)와
(배정밀도 부동소수점인 double)의 저장방식은 다음과 같다.
자료형 | 크기 | 부호 | 지수 | 가수 |
---|---|---|---|---|
float | 32bit | 1bit | 8bit | 23bit |
double | 64bit | 1bit | 11bit | 52bit |
float를 기준으로,
부호비트(1bit) = 양수면 0, 음수면 1 저장
지수비트(8bit) = 지수 + 127 (127을 저장하는 이유는 뒤에 설명)
가수비트(23bit) = 맨앞의 1을 제외, 24번째 bit에서 반올림
간단한 실수를 예로 실수의 부동소수점 저장방식 알아보기.
ex ) num = 13.8
부호비트(1bit) = 0 (양수)
13 = 1101 (정수부 2진수 변환)
0.8를 2진수로 저장하는 방법은 아래와 같다.
(*2 를 한 후 1.0보다 크면 1, 작으면 0.
1.0보다 클 경우 -1 한 후 1.0이 나올때까지 반복한다.)
차례대로,
0.8 * 2 = 1.6 ... 1
0.6 * 2 = 1.2 ... 1
0.2 * 2 = 0.4 ... 0
0.4 * 2 = 0.8 ... 0
0.8 * 2 = 1.6 ... 1
0.6 * 2 = 1.2 ... 1
0.2 * 2 = 0.4 ... 0
0.4 * 2 = 0.8 ... 0
0.8 * 2 = 1.6 ... 1
0.6 * 2 = 1.2 ... 1
0.2 * 2 = 0.4 ... 0
0.4 * 2 = 0.8 ... 0
.....
으로 반복된다.
따라서
0.8 = 110011001100... (소수부 2진수 변환)
13.8 = 1101.110011001100110011001100...
= 1.101110011001100110011001.. * 2^3
가수 = 1.101110011001100110011001..
가수비트(23bit) = 1011 1001 1001 1001 1001 101
***** 23bit로 제한되어있으므로 24번째에서 반올림.
***** 2진수 표현의 맨 앞은 항상 1이므로 따로 저장하지 않는다.
지수 = 3 (2^3 에서)
지수비트(8bit) = 3 + 127 = 130 = 1000 0010
***** 127을 더하는 이유 : 지수가 음수인 경우 (ex. 2^(-3)),
8bit로 표현하는 지수 부분에서 부호 비트를 사용하지 않도록 하기 위함.
***** 또한 2진수로 저장하므로 기수(밑수)는 2로 고정되어 있어 따로 저장하지 않는다.
마지막으로,
부호비트 + 지수비트 + 가수비트로 정리하면
01000001 01011100 11001100 11001101
이다.
이렇게 실수는 저장된다.
위의 예시를 꼭 한번 직접 해보고 읽어보기!
위에서 예를 든 13.8의 경우,
24번째 bit에서 반올림을 한 결과,
내부적으로 13.8은 실제 13.8보다 큰 값으로 저장되게 된다.
(23번째 bit는 0 이었는데 24번째가 1이어서 올림이 됨.)
그럼 만약 24번째 비트가 0이었다면?
내부적으로 실제 값보다 작게 저장되는 것이다.
이렇게 소수가 0.5의 거듭제곱(1/2의 거듭제곱)으로 나누어 떨어지지 않는이상 내부적으로 저장될 때 실제 값과 다르게 저장된다.
결과적으로 실수를 부동소수점으로 저장할 때
이런 경우에 의해 오차가 발생하게 되고,
시스템 또한 이를 최대한 비슷하게 저장하기 위해
10진수 표현에서도 최대한 비슷한 값으로 저장한다.
double num = 13.8;
저장된 num = (double)13.80000000000000071
내가 13.8을 저장했다고 해서 13.8이 저장된게 아니라
(double)13.80000000000000071로 저장됨.(실제보다 큼)
이렇게 실수를 저장하는 과정에서 오차가 발생하므로,
유의해서 사용할 필요가 있고( == 등으로 비교하면 안됨),
각 자료형의 필요한 정밀도에 따라 유효 자릿수에 맞게 사용하도록 하자.
우와 정리 너무 잘하셨네요 ^^ 하트 박고 갑니다~~~