[JS] 소수점 계산 오류 (배정밀도 64비트 부동소수점 )
by Roel Downey자바스크립트 숫자 타입의 값이 IEEE 754의 부동소수점 표현 형식 중 배정밀도 64비트 부동소수점 형식을 따르기 때문이다.
C나 자바의 경우, 정수와 실수를 구분해서 int, long, float, double 등과 같은 타입으로 정수와 실수를 구분해서 사용하지만, 자바스크립트의 경우 모든 수를 실수로 처리하기 때문에 하나의 숫자 타입만 존재한다.
옛날에는 컴퓨터의 성능이 좋지 않았기 때문에 이렇게 숫자의 타입의 크기를 지정해서 메모리를 효율적으로 사용하기 위해 타입이 분류되었다. 하지만 점점 컴퓨터의 성능이 좋아지면서 타입에 대해 덜 신경 쓰게 되었다.
배정밀도 64비트 부동소수점 부호부분
자바스크립트의 숫자 표현은 배정밀도 64비트 부동소수점 형식을 따른다고 했다. 여기서 부동소수점의 부동은 뜰부(浮)에 움직일동(動)이다. 즉 떠돌이 소수점(floating point)이란 뜻이다.
부동소수점 표현은 크게 세 부분으로 구성되는데 부호 부분, 지수 부분, 가수 부분이 있다.
0.1(십진법)을 배정밀도 64비트 부동소수점 형식으로 표현하는 방법은 아래와 같다.
- 부호부분
- 부호부분은 양수이면 0, 음수이면 1이 된다. 0.1은 양수이므로 부호부분은 0이 된다.
- 지수부분
- 0.1(10)을 2진법으로 바꾸면 0.00011001100...(2)으로 순환 소수가 나온다.
- 소수점의 자리를 1.xxx...가 되도록 왼쪽 또는 오른쪽으로 옮긴다. 0.00011001100...(2)에서는 오른쪽으로 4칸 이동해야 1이 나오기 때문에 식으로 표현하면 1.1001100...(2)×2^-4와 같다. 만약 1100.000110011000...(2)이라면 왼쪽으로 3칸 이동하고 표현식은 1.10000011001100...(2)×2^3이 된다.
- 지수부분을 구하려면 2^-4(1/2⁴)의 지수인 -4에다가 Bias를 더하고 2진법으로 변환해야 한다.
- Bias란 지수 편향을 말하는데, 이를 더하는 이유는 양수는 2진법으로 표현할 수 있지만, 음수는 표현할 수 없기 때문이다. 예를 들어 지수부분을 저장할 때 지수값이 1인 경우 2진법으로 변환해 00000000001로 저장할 수 있지만, -1인 경우 저장할 방법이 없다. 따라서 Bias를 더한 다음 2진법으로 변환하는 것이다.
- Bias를 구하는 식은 2^(지수부분의 총 비트수 - 1) - 1이다. 64비트의 지수부분은 11bit이므로 2^(11 - 1) - 1 = 1023으로 Bias는 1023이 된다. 그래서 0~1023에 해당하면 음수가, 1024~2047에 해당하면 양수가 저장되는 것이다.
- 이어서 계산하면 -4 + 1023 = 1019이다. 이 값을 2진법으로 변환하면 1111111011인데, 지수부분은 총 11bit이므로 앞에 0을 붙여서 01111111011이 들어가게 된다.
- 가수부분
- 가수부분은 소수점을 옮긴 1.1001100...에서 소수점의 오른쪽 부분이다. 소수점 왼쪽 부분은 무조건 1이기 때문에 따로 저장하지 않으며 이를 hidden bit라 한다. 여기서 오른쪽 부분은 계속 순환되므로 52비트에 넣으면 52번째 이후 값은 반올림을 해야한다. 원래 51, 52, 53번째 값은 011인데 53번째에서 반올림 했으므로 51, 52번째 값은 10이 된다. 만약 소수점의 오른쪽 부분이 52개보다 작다면 0으로 채워 넣는다.
IEEE 754란?
- IEEE 754는 IEEE(Institute of Electrical and Electronics Engineers) 즉 전기 전자 기술자 협회에서 개발한 것으로 컴퓨터에서 부동소수점을 표현하는 방법을 정의한 것으로 가장 널리 쓰이는 표준이다.
- IEEE 754 부동소수점을 표현하는 형식의 종류에는 2진법 부동소수점 형식과 10진법 부동소수점 형식이 있다.
이름 | 비트수 | 지수 비트 | 가수 비트 | 지수 편향 |
binary16 (반정밀도) | 16 | 5 | 10 | 15 |
binary32 (단정밀도) | 32 | 8 | 23 | 127 |
binary64 (배정밀도) | 64 | 11 | 52 | 1023 |
binary128 (사배정밀도) | 128 | 15 | 112 | 16383 |
decimal32 | 32 | 7 | 23 | 96 |
decimal64 | 64 | 9 | 53 | 384 |
decimal128 | 128 | 13 | 113 | 6144 |
※ binary = 2진법, bit = binary(2진법의) digit(숫자)의 약자, decimal = 10진법
결론
자바스크립트는 숫자를 배정밀도 64비트 부동소수점 형식을 따라 표현한다. 이것은 십진수인 소수를 이진수로 변환하고 소수점을 1이 나올 때까지 오른쪽 또는 왼쪽으로 옮긴 다음, 소수점 오른쪽에 해당하는 수를 가수부분에 넣게 되는데 이 수가 표현 자리수보다 넘어서게 되면 나머지 부분에서 반올림 처리를 하므로 근사값이 저장되면서 부정확하게 된다.
따라서 0.1 + 0.2의 결괏값이 0.30000000000000004가 나오게 되는 것이다.
소수점의 계산을 올바르게 하는 방법
- numObj.toFixed([digits])
- toFixed()는 Number객체에 주어진 digits 만큼의 소수점 이하 자리수를 정확하게 갖는 문자열 표현으로 반환한다.
- 소수점 이하가 길면 숫자를 반올림하고, 짧아서 부족할 경우 뒤를 0으로 채운다.
- digits의 옵션은 0 이상 20 이하의 값을 사용할 수 있고, 구현체에 따라 더 넓은 범위의 값을 지원할 수도 있다. 값을 지정하지 않으면 0을 사용한다.
- Math.round(x)
- 입력값을 반올림한 값과 가장 가까운 정수를 반환한다.
이것을 응용해 0.3을 만들 수 있다.
'Web > JavaScript' 카테고리의 다른 글
[JS] __proto__가 [[Prototype]]으로 표시 (0) | 2022.01.03 |
---|---|
[JS] 연산자 (Operator) (0) | 2022.01.03 |
[JS] 데이터 타입 (0) | 2021.12.29 |
[JS] 변수 (0) | 2021.12.29 |
[JS] <script> 태그의 defer 속성 (0) | 2021.12.28 |
블로그의 정보
What doing?
Roel Downey