10진수 소수 <-> 2진수 소수 상호 변환
이번 글에서부터는 컴퓨터가 '실수'를 어떻게 표현하는지 정리하고자 한다.
이진수를 다루는 컴퓨터가 실수를 어떻게 표현하는지를 알기 위해 먼저 10진수의 소수가 어떤 식으로 표현되고 있는지를 생각해보자.
10.155 라는 소수는
1 x 10^1
0 x 10^0
1 x 10^-1
5 x 10^-2
5 x 10^-3
이렇게 표현된다는 건 자연스럽게 알고 있다.
이는 이진수에서도 마찬가지로 작용한다.
10.111 이라는 이진수 소수는
1 x 2^1
0 x 2^0
1 x 2^-1
1 x 2^-2
1 x 2^-3
이렇게 표현된다.
그리고 이 값을 10진수로 잘 계산해주면 우리가 사용하는 10진수 소수값이 나온다.
그리고 이 과정을 통해 자연스럽게 2진수를 10진수로 변환하는 방법도 정리하게 되었다!
그렇다면 10진수 소수를 2진수 소수로 어떻게 변환할 수 있을까?
10진수 정수를 2진수 정수로 변환할 때와 반대로, 계속 2를 곱하면서 소수점이 사라지고 정수만 남는 순간에 멈춘다.
그리고 그 정수를 2진수로 변환한 다음, 2를 곱한 횟수만큼 소수점을 왼쪽으로 옮겨주면 된다.
0.875 라는 10진수 소수를 2진수 소수로 바꿔보자.
한번 2를 곱하면 1.75 / 2^1
두번 2를 곱하면 3.5 / 2^2
세번 2를 곱하면 7 / 2^3
정수가 나왔으니 이를 2진수로 변환하면
111(2)
2를 3번 곱했으니 소수점을 세번 왼쪽으로 옮겨주면
0.111(2)
이를 10진수로 역산해보면 0.5 + 0.25 + 0.125 = 0.875 가 나온다.
그런데 항상 모든 10진수 소수를 2진수 소수로 깔끔하게 바꿀 수 있는 것은 아니다.
유명한 예시인 0.1 이라는 10진수 소수를 2진수 소수로 바꿔보자.
한번 2를 곱하면 0.2
두번 2를 곱하면 0.4
세번 2를 곱하면 0.8
네번 2를 곱하면 1.6
...
열번 2를 곱하면 102.4
....
2를 아무리 거듭제곱해도 맨 끝자리는 2 4 8 6 를 순환하기 때문에 절대 정수형이 나올 수가 없다.
그래도 102.4 는 반올림했을 때 102와 비슷하다고 볼 수 있으니 한번 102라고 가정하고 이진수로 변환해보자.
102 = 64 + 32 + 4 이므로 1100100(2)
2를 10번 곱했으므로, 소수점을 왼쪽으로 0번 옮겨주면 된다.
0.0001100100
하지만 이 값은 완벽히 0.1과 동일하지는 않다.
그래서 파이썬에서는 (0.1 + 0.1 + 0.1 == 0.3) 이라는 식의 값이 True 가 아니라 False로 나온다.
물론 이런 문제를 해결하기 위한 여러 방법이 존재하겠지만,
지금은 어떻게 컴퓨터가 사용하는 2진수로 10진수 소수를 나타낼 수 있을까에 대한 방법에 초점을 맞춰 정리하고자 한다.
Floating point representation (부동 소수점)
소수점은 표현하는 방식으로 Scientific notation 이 있다.
이는 정수부분에 숫자를 하나만 남겨두고 나머지 숫자는 모두 실수부로 옮긴다.
그 뒤, 이렇게 표현하기 위해 이동시킨 소수점의 위치 만큼 해당 진법수의 거듭제곱으로 보간해준다.
0.1 x 10^-8 => 1.0 x 10^-9
0.00315576 x 10^12 => 3.15576 x 10^9
이렇게 변환했을 때 3.15576 이 부분을 Significand 라고 부르고, 10^9 의 9 를 Exponent 라고 부른다.
(사실 0.1 x 10^-8도 scientific notation 정의에 맞는 수라고 한다. 0도 하나 남긴 숫자로 볼 수 있다.)
여기에 더해, 정수부분에 '0이 아닌' 숫자를 남기도록 표현한 것을 Normalized numbers 라고 한다.
위에서 변환한 3.15576 x 10^9, 1.0 x 10^-9 모두 Normalized number 이다.
만약 소수표현을 2진수로 했다면, 2진수에서는 0이 아닌 값이 반드시 1이므로, 언제나 1.pppppp x 2^qqqqq 형태로 나타날 것이다.
이때 저 1. 부분을 뺀 pppppppppp 부분을 fractional part 라고 부르고, qqqqqq 는 그 직전에 말했듯, Exponent 라고 부른다.
컴퓨터에서 실수를 표현할 때는 해당 실수를 Normalize 한 뒤,
fractional part 와 exponent 2가지에 더해 '부호(Sign)' 까지 총 3가지 정보를 저장함으로써 실수를 표현한다.
이런 표현 방식을 부동소수점 (뜰 부, 움직일 동 -> 둥둥 떠서 움직인다. 즉 소수점의 위치가 유동적으로 바뀐다는 의미이다.) 방식이라고 한다.
이와 같은 소수점 표현방식은 IEEE (I triple E) 라는 곳에서 기준을 정의하였으며, 이 기준의 이름은 ANSI IEEE 754-1985 이다. (왠지 시험에서 단답형으로 물어보기 좋을 것 같은 이름이니 외워두고 싶다.)
소수점을 부동 소수점으로 표현할 때는 3가지 포맷이 있다.
32bit 로 표현하는 single format (단정밀도)
64bit 로 표현하는 double format (배정밀도)
128bit 로 표현하는 quadruple format (4배정밀도)
수업에서는 배정밀도까지만 다룬다.
IEEE Floating-Point Format
그렇다면 sign, fraction, exponent 이 3가지는 각 format에서 어느 정도 사이즈로 저장이 되는지 살펴보자.
먼저 단정밀도에서는 아래와 같이 할당된다. (시험에 나오니 외워두라고 하셨다!)
sign = 1bit
exponent = 8 bit
fraction = 23 bit
sign 비트는 1비트로 충분하다는 것은 어렵지 않게 이해할 수 있다.
0이면 음수가 아니고, 1이면 음수로 인식한다. (-1) ^ sign 과 같다.
exponent 는 8비트를 잡아준다.
이때 exponent 는 biased 된 값을 사용한다.
exponent 가 음수가 될 수 있기 때문인데, 항상 양수로 표현이 되도록 특정 값을 더해서 저장하고, 실제 실수 계산을 할 때는 다시 그 특정값을 빼서 계산한다.
단정밀도에서의 bias 값은 127 이다.
exponent 가 0인 경우는 생각을 안하는가? 왜 '0이상인 수' 가아니라 '양수' 인가? 라고 한다면
exponent 가 0인 경우는 특수한 경우를 처리하기 위해 사용되므로 후술한다.
따라서 실제 exponent 값으로 가능한 범위는 -127 부터가 아니라 -126부터 임에 주의하자.
fraction은 유효숫자와 관련된 부분이라 그런지 bit 를 크게 잡아주었다.
위에서 적었듯 1.xxxx 에서 xxxx 부분만을 저장하고 있기 때문에, 이 fraction으로 표현되는 significand 의 범위는 아래와 같다.
1.0 <= | significand | < 2.0
그리고 bit 내 순서는 위에서 작성한 순서대로 차지한다.
즉, 아래와 같다.
sign = 31 bit
biased exponent = 30 ~ 23 bit
fractional part of significand = 22 ~ 0 bit
배정밀도의 비트 구성은 설명없이 결론만 간단하게 정리만 한다.
sign = 1 bit
exponent = 11 bits
fraction = 64 - 12 = 52bits
bias = 1023
10진수 소수의 단정밀도 소수 변환 예제
한번 -0.875 의 10진수 소수를 IEEE 754 부동소수점 방식의 단정밀도 소수로 표현해보자.
1. 먼저 -0.875 를 2진수 소수로 변환한다. => - (0.5 + 0.25 + 0.125) => -0.111
2. 이를 normalized number 로 바꾼다. => (-1) * 1.11 * 2^-1
3. sign, exponent, fraction 값을 찾는다.
sign = 1
fraction = 11
exponent = -1
4. fraction은 23 비트를 왼쪽부터 채우고 남는자리는 모두 0으로 채운다.
1100 0000 0000 0000 0000 000
5. exponent 는 bias 값인 127을 더한 결과 값을 2진수로 표현한다.
-1 + 127 = 126
126 = 64 + 32 + 16 + 8 +4 + 2
126 = 1111110(2)
이 값을 8bit expoent 공간에 오른쪽에 맞춰 채우고, 남는 왼쪽 공간은 0으로 채운다.
126은 7비트 값이므로 맨왼쪽 bit 는 0이 된다.
01111110
이제 sign -> exponent -> fraction 순서로 맞춰서 비트를 채운다.
1 01111110 1100 0000 0000 0000 0000 000
2진수 표기가 보기 힘들어서 이를 16진수로 바꾸어야 하는 경우도 있다.
1011 1111 0110 0000 0000 0000 0000 0000
=> 0xBF600000
단정밀도 이진수 소수의 10진수 변환
이번엔 거꾸로 단정밀도로 주어진 이진수 소수를 10진수 소수로 바꿔보자.
이진수는 보기 힘드니 16진수로 주어지는 경우도 있다.
0xC0A00000
=> 1100 0000 1010 0000 0000 0000 0000 0000
여기에서 거꾸로 sign 비트, exponent 비트, fraciton 비트로 나눈다.
1 10000001 01000000000000000000000
sign = 1
biased expoent = 10000001 = 129
fraction = 010000...
이제 exponent 의 bias 를 제거해주면 129 - 127 = 2
따라서 구하는 소수는
(-1) ^ 1 * (1.0100000...) * 2^2
= -1 * 101.00000
= -5.0
Single-Precision Special Numbers
이번엔 단정밀도에서 특수한 수를 나타내는 경우를 살펴보자.
아까 biased exponent 는 항상 '양수' 라고 하였다.
그렇다면 biased exponent 가 0 인 경우는 어떤 경우인지 살펴보자.
Exponent | Fraction | Meaning |
255 | != 0 | NaN (Not a Number) ex: sqrt(-1) |
255 | == 0 | +/- Infinite |
0 < E < 255 | number | |
0 | != 0 | subnormal |
0 | == 0 | 0.0 |
시험용으로 한다면 그냥 외울 수 밖에 없는 내용이니만큼, 나름대로 그 유래를 추측하면서 외워보자면..
exponent 가 255라면 일단 굉장히 큰 수다. (bias를 빼면 128이므로 표현 가능한 제일 큰 지수)
그렇다면 둘 중 하나가 될 것이다.
엄청 큰 수를 의미하거나, 오버플로우가 일어나거나
나는 1.0 * 2^128 이 표현 가능한 최대 큰 수가 될 거라고 생각했다.
이렇게 생각하면 1.0 * 2^128 이 가장 큰 수, 즉 무한대를 의미하게 되고,
이 값을 넘어가는 1.xxx * 2^128 은 무한대를 넘어간 (오버플로우 된) 수를 의미하여 표현불가, NaN이 된다고 외워보았다.
만약 exponent 가 0 이라면 일단 굉장히 작은 수다. (bias 를 빼면 -127 이므로 표현 가능한 제일 작은 지수)
이 상황에서 fraction 조차 0이면 그냥 제일 작은 수 0.0 이 된다고 생각했고,
fraction 이 0이 아니면 그래도 작은 무언가를 표현하려고 했다는 의미로 subnormal 이라고 외워보았다.
이렇게 생각하니까 잘 외워진다.
여기에서 subnormal 은 0에 가까운 작은 수를 표현하는 방식이다.
기존의 표현방식이 significand 를 1.xxxxx 형태로 표현했다면, subnormal 은 significand 부분을 0.xxxx 의 형태로 보는 것을 말한다.
exponent는 -126으로 고정시켜두고, fraction 부분만 신경쓰므로 기존에 표현 가능한 범위보다도 더 작은 수를 표현할 수 있다.
Single-Precision Floating-Point Range
이번엔 한번 단정밀도가 표현가능한 수의 범위를 생각해보자.
Exponent 는 0 < E < 255 이고, Fraction 의 값은 제한이 없다.
그러면 제일 작은 절댓값은 1.0 * 2 ^ -126 이 된다.
그리고 제일 큰 절댓값은 1.(1이 23개) * 2^127 이 되는데, 1.111111111 은 사실상 2.0에 가까운 수 이므로, 2^128이 표현가능한 제일 큰 숫자가 된다. (소수점 형태로 나타내면 2.0 * 2^127)
(1.0) * (2 ^ -126) <= N < 2.0 * 2^127
이를 10진수로 표현하면, 1.1754... * 10^-38 <= N < 3.40282347 * 10^38 이라고 하는데,
그냥 아주 작은 수부터 꽤 큰 수까지 표현가능하구나.. 정도로 받아들이면 될 것 같다.
여기에 더해 subnormal 까지 생각해보자.
subnormal 은 지수부를 2^-126 으로 고정하고, 실수부를 0.xxxxx 형태로 표현한 부동소수점 표현이다.
이렇게 함으로써, 0 <= N < (1.0) * 2^-126 범위를 표현할 수 있게 된다.
10진 소수의 배정밀도 2진소수변환 예제
다음으로 간단하게 배정밀도 변환 예제도 하나 살펴보자.
단정밀도와 방식은 완전히 동일하다.
-0.6875를 배정밀도 부동소수점 방식의 이진수로 바꿔보자.
-0.6875
= - (0.5 + 0.125 + 0.0625)
= -0.1011
= -1.011 * 2^-1
sign = 1
exponent = -1
fraction = 011
배정밀도의 exponent bias 는 1023 이므로 -1 + 1023 = 1022
1022 = 512 + 256 + 128 + 64 + 32 + 16 + 8 + 4 + 2 이므로 1111111110 (2)
배정밀도 표현에서의 exponent 는 11 bit 이고 fraction 은 52 bits 이다.
따라서 배정밀도로 이 소수를 표현하면
1 01111111110 011(+49개 0)
이다.
이 값은 굳이 16진수로 바꾸진 않겠다..
배정밀도 이진소수의 10진소수 변환 예제
1 10000000001 1100...00 이렇게 표현된 소수를 10진 소수로 바꾸어보자.
sign = 1
biased exponent = 10000000001 = 1025
fraction = 1100.... => 1.11 (2)
bias 를 제거하면 그냥 exponent 는 1025 - 1023 = 2
따라서 구하는 소수는 (-1)^1 * 1.11 * 2^2 = -7.0
Double-Precesion Special Values
배정밀도에서 biased exponent 가 가질 수 있는 수의 범위는 11bit 이므로 0 ~ 2047 이다.
배정밀도도 마찬가지로 exponent 가 0 일때와 2047일 때 특별한 의미를 가지며, 이는 단정밀도와 동일하다.
exponent = 2047, fraction == 0 => 제일 큰 수이므로 무한대
exponent = 2047, fraction != 0 => 제일 큰 수를 넘었으므로 오버플로우 NaN
exponent = 0, fraction == 0 => 제일 작은 수이므로 0.0
exponent = 0, fraction != 0 => 뭔가 작은 수를 표현하려고 하므로 subnormal
그 밖의 값에서 표현 가능한 범위는
1.0 * 2^-1022 <= N < 2.0 * 1023
대충 이 값은 2.22507~ * 10^-308 <= N < 1.797693~ * 10^308 이라고 한다.
암튼 엄청 작은 값이랑 엄청 큰 값을 다룬다.
Quadruple Precision Floating Point
128bit 를 이용하여 소수를 표현하는 방식이다.
sign = 1 bit
exponent = 15 bit
fraction = 112 bit
bias = 16383
Quadruple Precision Floating Point도 exponent 값에 따라 특별한 값을 똑같이 의미할 것이라는 걸 유추할 수 있다.
4배정밀도는 여기까지만 알아보자.. (여기서부터는 시험 범위도 아니라고 하신다.)
사실 이 정도 정밀도쯤 오면, 거의 천문학 scale 에서 사용하는 초정밀 연산이 되지 않을까 싶을 정도다.
지금까지 컴퓨터가 소수를 표현하는 방식은 ANSI IEEE 754-1985 의 부동소수점 표현 방식에 대해 정리하였다.
다음 글에서는 Floating Point 로 표현한 숫자간 연산을 정리해보려고 한다.
'CS > 어셈블리' 카테고리의 다른 글
[SPARC] 38. FPU Instructions 사용 예제 (단정밀도) (0) | 2023.12.08 |
---|---|
[SPARC] 37. 실수 계산 & FPU & FPU Instructions (0) | 2023.12.07 |
[SPARC] 35. Bubble Sort 구현 (2) | 2023.12.07 |
[SPARC] 34. Leaf Subroutine & Pointer Type Argument (0) | 2023.12.06 |
[SPARC] 33. 구조체 return 하기 (0) | 2023.12.05 |