요약
프로그램 속의 소수부가 있는 수는 수학의 실수가 아니다. 그것은 분모가 2의 거듭제곱인 유리수들로 이루어진 유한한 부분집합이며, 이 부분집합에 속하지 않는 값은 모두 속하는 값 가운데 가장 가까운 것으로 바뀐다. 이 수업에서는 부동소수점 수가 64비트(부호, 지수, 가수)에 어떻게 저장되는지, 왜 에는 정확한 표현이 없고 어떤 값이 그 자리를 차지하는지, float 타입과 double 타입에서 몇 자리가 안전한지, 반올림 오차가 어떻게 누적되는지, 왜 두 실수를 == 연산자로 비교하지 않으며 근거 있는 허용 오차로 어떻게 비교하는지, 그리고 이 형식이 담을 수 없거나 존재하지 않는 결과를 위해 남겨 둔 무한대와 비수가 무엇인지를 공부한다.
학습 목표
- 왜 에는 정확한 이진 표현이 없는지, 그 대신 어떤 값이 저장되는지 설명한다.
- 반복된 덧셈과, 크기가 크게 다른 수들의 덧셈에서 반올림이 미치는 영향을 예측한다.
- 실수를
==연산자 대신 알맞은 허용 오차로 비교하고, 그 허용 오차를 고른 근거를 댄다. - 필요한 정밀도에 따라
float타입과double타입 가운데 하나를 고르고, 무한대와 비수를 알아본다.
밑이 2인 과학적 표기법
정수와 그 표현 수업에서는 비트 정수가 정확히 개의 연속된 값을 나타낸다는 것을 보았다. 그러나 소수부가 있는 수에는 다른 어려움이 있다. 어떤 두 실수 사이에도 다른 실수가 무한히 많으며, 유한한 개수의 비트로는 그것들을 모두 구별할 수 없다. 오늘날의 모든 컴퓨터가 택하는 해법은 과학이 매우 큰 양이나 매우 작은 양을 적을 때 쓰는 방법과 같은 과학적 표기법이다. 밑이 10일 때 지구에서 태양까지의 평균 거리는 미터로 적는다. 이 수에는 부호와 몇 개의 유효숫자(가수, )와 밑의 거듭제곱(지수, )이 있으며, 지수는 소수점이 어디에 오는지를 알려 준다. 그렇다면 고정된 자릿수로 크기의 차수가 매우 다른 양들을 다룰 수 있고, 그 대가는 각 양의 앞쪽 몇 자리만 저장한다는 것이다.
컴퓨터는 같은 일을 밑을 2로 하여 한다. 0이 아닌 모든 수는 다음과 같이 단 한 가지 방식으로 쓸 수 있다.
여기서 의 값은 부호(양수이면 0, 음수이면 1)이고, 의 값은 이진법으로 쓴 가수로 첫 자리가 언제나 1이며, 의 값은 지수이다. 첫 자리가 언제나 1이라는 사실에는 실용적인 결과가 따른다. 그 자리를 저장할 필요가 없으므로 아낀 1비트를 정밀도에 쓸 수 있다. 부동소수점이라는 이름은 바로 이 형태를 가리킨다. 소수점이 자리들 사이에서 고정된 위치를 차지하지 않고 지수에 따라 ‘떠다닌다’.
C++의 double 타입은 현행 모든 컴파일러에서 국제적으로 표준화된 64비트 배정밀도 이진 형식을 쓴다. 부호에 1비트, 지수에 11비트, 가수에 첫 자리를 뺀 52비트이다. 지수는 치우쳐서 저장된다. 11비트 필드에는 의 값이 들어가므로, 부터 까지의 지수에 대해 1부터 2046까지의 부호 없는 값을 저장한다(필드의 값 0과 2047은 0, 매우 작은 수, 무한대, 비수를 위해 예약되어 있으며, 이것들은 수업 끝에서 다룬다). float 타입은 32비트 단정밀도 형식을 쓰며, 부호에 1, 지수에 8, 가수에 23비트를 둔다.
double 값의 비트는 눈으로 볼 수 있다. 아래 프로그램은 std::bit_cast 함수(헤더 <bit>에 있다)를 쓴다. 이 함수는 수의 64비트를 하나도 바꾸지 않고 64비트 부호 없는 정수로 다시 해석한다. 여기서는 더 설명하지 않고 메모리를 들여다보는 도구로만 쓴다. {:064b} 형식 지정은 지난 수업에서처럼 그 정수를 이진법 64자리로 쓰고, {:016X} 형식 지정은 십육진법 16자리로 쓴다.
#include <bit>#include <cstdint>#include <print>int main(){ const double tenth{0.1}; // 수의 64비트를 부호 없는 정수로 읽은 것. const std::uint64_t bits{std::bit_cast<std::uint64_t>(tenth)}; std::println("{:064b}", bits); std::println("{:016X}", bits);}00111111101110011001100110011001100110011001100110011001100110103FB999999999999A그림은 프로그램이 출력한 64비트를 묶어서 필드별로 읽는다.
증명
64비트를 십육진법으로 쓰면
3FB999999999999A값이다. 왼쪽에서 오른쪽으로 부호 1비트, 지수 11비트, 가수 52비트로 읽는다.부호는 0이다. 수는 양수이다.
지수 필드는 이진법으로 이며 그 값은 1019이다. 여기서 치우침 1023을 빼면 지수는 의 값이 된다.
가수는 저장되지 않는 첫 1을 포함하면 이진법으로 의 꼴이다. 의 묶음이 되풀이되고, 마지막 묶음 은 올림되어 있다. 그 값은 대략 이다.
저장된 수는 , 곧 의 값이다. 에 아주 가깝지만 그 자체는 아니다.
콘텐츠를 불러오는 중…