문제

개발자 도구 콘솔 창에서 0.1 + 0.1 + 0.1을 실행해보자.

0.1 + 0.1 + 0.1
//0.30000000000000004

우리가 원하는 값은 0.3인데, 정확한 0.3이 나오지 않고 약간의 오차가 생겼다.

원인

Javascript는 수를 표현할 때 IEEE 754방식을 사용하는데, 0.5, 0.25 등을 제외한 대부분의 소수는 이 방식으로 표현이 불가능하기 때문에 표현하기 위해 반올림을 사용한다.
이 문제는 해당 반올림 과정에서 미세한 오차가 생기기 때문에 일어난다.

IEEE 754 표현법

IEEE 754 표현은 다음 그림과 같이 부호, 지수부, 가수부로 크게 세 부분으로 나누어져 있다.
IEEE754 표현

일반적으로 많이 쓰이는 형식은 32bit 단정도와 비트 수를 두배로 한 64bit 배정도이다.

예시

-118.625를 32bit 단정도를 IEEE 754로 표현해보자.
32bit 단정도에서는 지수부를 8bit, 가수부를 23bit로 잡고 있다.

  1. 음수이므로 부호는 1이다.
  2. 118.625를 이진수로 변환하면 1110110.101이 된다. [이진수 변환 참조]
  3. 앞자리에 1만 남을 때 까지 소수점을 앞으로 옮긴다. (이 과정을 정규화라고 한다)
    • 1110110.101 -> 1.110110101 * 26
  4. 가수부에는 110110101이 남아있으며, 이를 23bit로 만들기 위해 0을 채워넣는다.
    • 1.110110101 * 26 -> 1.11011010100000000000000 * 26
  5. 지수부에는 2의 지수에 127(32bit의 bias)를 더한 수를 넣는다. 즉 6 + 127 = 133이므로, 10000101이다.
    • 지수부는 8비트이므로 -127부터 127을 0부터 255까지 저장하게 된다.
      따라서 127이 실질적인 0이 되므로 127만큼을 더해주어야 한다.

결과는 다음과 같이 나타난다.
11000010111011010100000000000000

반올림 규칙

앞서 서술하듯이 이 표현법으로는 대부분의 소수를 표현하지 못한다.
예를 들어 0.3은 이진수로 변환하면 0.01001100110011001...로 순환한다. 때문에 IEEE 754에서는 이를 처리하기 위한 5가지 반올림 규칙을 정해두었다.

  • round to nearest, ties to even (짝수로 반올림)
    • 가장 많이 사용하는 방식으로, 반올림을 할 때 가까운 숫자가 두 개이면 가수부의 마지막 자리가 짝수인 값으로 반올림 한다.
  • round to nearest, ties away from zero (0에서 먼 방향으로 반올림)
    • 반올림을 할 때 가까운 숫자가 두 개이면 절대값이 큰 값으로 반올림한다.
  • 올림
  • 버림
  • 절삭
    • 원래 값보다 절대값이 작거나 같은 값을 선택한다.

즉, 0.3은 1.001100110011001... * 2-2로 변환 할 수 있는데, 가수부는 23bit를 차지하므로 소수점 아래 24번째 자리에서 반올림을 하면 가수부의 값은 00110011001100110011010이 된다.

결론적으로 0.3을 정규화하면 1.00110011001100110011010 * 2-2이고, 이는 사실상 0.30000001192092895508이라는 0.3과 약간의 오차가 있는 소수로 나타.

덧셈

IEEE 754 형식의 사칙연산을 할 때, 지수부가 같은 경우에는 일반적인 이진수 계산을 하면 되지만 지수부가 다른 경우에는 지수가 작은 쪽을 따른다.

예시

0.1 + 0.1 + 0.1을 계산해보자. 0.1을 정규화하면 1.10011001100110011001101 * 2-4이다.
이 경우는 지수부가 같으므로, 지수 조정 없이 이진수 덧셈을 하면 된다.
해당 연산의 결과는 11.00110011001100110011010 * 2-4이 되는데, 이 값을 정규화 및 반올림하면 1.10011001100110011001101 * 2-3이라는 값이 나온다.

여기에 다시 1.10011001100110011001101 * 2-4을 더하면 100.11001100110011001100111 * 2-4이 되며,
최종적으로는 1.00110011001100110011010 * 2-2으로 나타낼 수 있다. 이는 앞서 반올림 규칙에서 정규화했던 0.3과 동일한 값을 나타낸다.

여기에서는 32bit 단정도를 사용하여 비교적 큰 단위에서 반올림을 해 0.3이 나타나게 되나, 64bit 배정도를 사용해서 계산하면 0.30000000000000004이 나온다.

결론

Javascript를 포함한 많은 프로그래밍 언어는 수를 표현할 때 IEEE 754를 사용하고 있으며, 이 언어들 역시 연산을 실행하면 반올림 방법에 따라 차이가 있을 순 있으나 약간의 오차를 가지고 있다.
따라서 이는 프로그래밍 언어의 설계나 연산능력의 문제가 아닌, 약속된 숫자 표현 자체의 한계점이라고 볼 수 있다.
미래에 소수점 아래 값들을 표기하는 획기적인 방법이 나오거나 컴퓨터 구조 자체가 뒤집히지 않는 이상, 우리가 사용할 단위 아래 값들은 반올림을 하거나 버리는 등의 정제를 하여 사용하는 수 밖에 없겠다.

+ Recent posts