프로그래밍 언어 안에서는 실수를 표현할 때 주로 부동소수점을 사용하게 된다.
대표적으로 Java와 float와 double을 주로 사용하고 Javascript에서는 number를 이용한다.
하지만 이러한 타입들을 이용하여 연산을 할 때 오차가 발생할 수 있기 때문에 신경써야 한다.
컴퓨터는 0과 1로 이루어진(2진법) 기계어를 사용한다. 하지만 사람은 수를 표현할 때 기본적으로 10진법을 이용한다.
정수의 경우에는 간단하게 10진수의 숫자를 2진수로 표현하기 쉽다.
예를들어 10진수인 18을 2진수로 변환하면 1001
이 될 것이다.
정수는 위와 같이간단하지만 컴퓨터에서 실수 (real number)를 표현하려면 어떻게 할까?
정수 부분은 위에서와 같이 손쉽게 2진수로 변환을 하면 된다. 문제는 소수점인데 정수부와 동일하게 숫자를 하나씩 2진수로 바꿔버리면 안되나 생각을 할 수 있지만 그렇게 하면 서로 다른 10진수 숫자가 2진수로 변환되었을때 중복이 되는 문제가 발생한다.
1.9 => 1.1001
1.41 => 1.100 1
그렇기 때문에 실수는 정수의 정 반대의 연산을 하게 된다. 정수는 2진법으로 변경하기 위해 2로 나눠가는 반면 실수는 2를 곱해가면서 1이나 0을 얻어오게 된다.
0.625
0.625 * 2 = 1.25 => 1을 빼고 나머지 0.25
0.25 * 2 = 0.5 => 0을 빼고 나머지 0.5
0.5 * 2 = 1 => 1을 배고 나머지 0
위와 같이 2를 계속해서 곱해가다 0이나오면 종료하고 결과를 위에서 부터 읽어준다 즉 0.625
는 0.101
이 된다.
고정소수점 표현 방식이라는 것은 쉽게 말해 10진수를 2진수로 바꾸고 그것을 그대로 사용하는 방식이다.
예를 들어 118.625(
라는 실수가 있으며 해당 실수를 2진수로 변환하면 1110110.101
이 되며 이것을 그대로 저장하게 된다.
16비트 체계를 쓴다고 하면 01110110.10100000
이와 같이 저장을 하게 되는데
첫번째 bit는 부호 비트이며 나머지는 정수부와 소수부로 채워지게 된다.
소수부의 경우 앞에서부터 채우게 되며 남는 뒷자리는 다 0으로 채우게 된다.
이러한 고정 소수점 방식은 구현하기 편하지만 사용하는 비트 수 대비 표현할 수 있는 수의 범위가 적고 정밀도가 낮기 때문에 실수를 다룰 필요가 있는 서비스에서는 거의 사용되지 않는다.
부동 소수점 방식은 실수를 2진법으로 변경한 것을 그대로 사용하는 것이 아니라 몇가지 과정을 추가적으로 거친 후 저장을 하게 된다.
정규화라는 단어는 수학이나 컴퓨터 분야에서 다양한 의미로 쓰이지만 여기서 말하는 정규화라는 것은 2진수를 1.xxx... * 2^n
꼴로 변환하는 것을 말한다.
변환하는 방법은 간단하다. 정수부에서 1만 남을 때 까지 소수점을 왼쪽으로 이동시키고 이동한 칸 수 만큼 위의 식에서 n자리에 집어넣으면 된다. (정수부가 0인 경우에는 오른쪽으로 이동한다.)
예를 들어 위에서 봤던 1110110.101
을 정규화 하면 1.110110101 * 2 ^ 6
가 된다.
IEEE 표준에 따르면 부동소수점 방식으로 실수를 저장하는데 32비트 혹은 64비트가 사용되며 32비트 기준으로 아래 그림과 같은 구조를 가지게 된다.
부호 비트는 고정소수점과 동일하게 0이면 양수, 1이면 음수를 의미한다.
정규화 된 결과 소수점 오른쪽에 있는 숫자들을 왼쪽부터 그대로 넣으면 되며 남은 자리는 0으로 그대로 채우게 된다.
지수부는 위의 계산결과(1.110110101 * 2 ^ 6
)에서 n의 위치에 있는 6
를 2진수로 바꾼 10
을 그냥 넣는 것이 아닌 bias
라고 하는 지정된 숫자를 더한 다음에 넣어야 한다.
IEEE 표준에서는 32비트를 쓰는 경우 bias
를 127
로 규정하고 있다. 따라서 32비트 기준 6 + 127 = 133
를 2진수로 바꾼 10000101
가 들어가게 된다.
그럼 bias
를 왜 쓸까? 그 이유는 지수가 음수가 될 수도 있어서 그렇다. 예를들어 0.000101
이라는 이진수가 있을 때 정수부가 0이기 때문에 왼쪽이 아닌 오른쪽으로 소수점을 밀게 되는데 그럼 1.01 * 2 ^ -4
가 된다.
위의 결과 (1.01 * 2 ^ -4
)에서 -4
를 지수에 어떻게 저장할 것인가? 맨 앞 부호비트가 있다고 해도 해당 부호 비트는 지수부 만을 위한 부호비트가 아니다. 그렇기 때문에 bias
을 더하여 32비트 기준 0 ~ 127
구간은 음수, 128 ~ 255
구간은 양수를 표현하도록 만든 것이다.
위에서 살펴본 것과 같이 32비트 체계를 32비트 단정도 (Single-Precision), 64비트 체계를 64비트 배전도 (Double-Precision) 라고 부른다.
프로그래밍 언어에서 흔히 접할 수 있는 실수형타입 float, double
는 각각 전자, 후자에 해당한다.
float
는 부동소수점 방식을 사용하는 기본형이라는 의미로 floating point
에서 따왔으며 double
는 64비트 배정도를 사용한다는 의미로 double-precision
에서 따왔을 것이라 추측된다.
double
는 64비트 체계에서 지수부가 11비트, 가수부가 52이다. 지수부가 2 ^ 11
즉 2048개의 수를 표현할 수 있으므로 0 ~ 1023
구간은 음수, 1024 ~ 2047
구간은 양수 지수를 의미하며 bias
는 1023이 된다.