JavaScript의 숫자는 IEEE 754 표준에 따라 저장되는 모두 부동 소수점입니다.
이 표준에는 여러 형식이 있습니다. 이 중 자바스크립트는 binary64 또는 double percision을 사용합니다.
binary64는 숫자를 64비트의 이진 형식으로 저장됩니다.
숫자에 경우에는 비트 0~51까지 52비트를 사용하며, 지수는 52~62까지의 11비트를 부호는 마지막 63번째 비트를 사용합니다.
부호의 sign 비트는 0이면 숫자가 양수이고 그렇지 않으면 음수를 나타냅니다. 대략적으로 fraction(분수)는 숫자의 자릿수를 포함하는 반면 지수는 소수점이 어디 위치하는지를 표현합니다.
자바스크립트는 숫자를내부적으로 바이너리로 저장하지만 기본 출력은 십진수입니다.
sign(부호) (1 bit) 63
|
exponent(소수점 위치) (11 bit) 62 52
|
fraction (숫자) (52 bit) 51 0
|
자바스크립트를 이용해 소수점의 계산을 다음과 같이 진행했습니다.
0.1과 0.2의 합이 0.3과 일치해야 하는데 그 결과는 위의 그림과 같습니다.
숫자는 0과 1로 이루어진 이진수로 변환되어 연속된 메모리 공간에 저장됩니다. 그런데 10진법을 사용하면 쉽게 표현할 수 있는 분수(fraction)는 이진법으로 표현하면 무한 소수가 됩니다.
예를 들어 0.1을 분수로 표현하면 1을 10으로 나눈 수인 1/10으로 표현할 수 있습니다. 10진법을 사용하면 이러한 숫자를 쉽게 표현할 수 있습니다.
그러면 1/3을 10진법을 사용해서 표현해봅시다. 그 결과는 0.333333333...으로 무한소수가 됩니다. 이렇게 10의 거듭제곱으로 나눈 값은 10진법에서 잘 동작하지만 3으로 나누게 되면 10진법에서 제대로 동작하지 않습니다.
이와 같은 이유로 2진법 체계에서 2의 거듭제곱으로 나눈 값은 잘 동작하지만 1/10과 같이 2의 거듭제곱이 아닌 값으로 나누게 되면 무한 소수가 되어 버립니다.
즉 분모를 소인수분해했을 경우 2의 거듭제곱 형태가 아닌 경우 부정확한 십진수 출력이 된다는 점입니다.
10진법에서 1/3을 정확하게 표현할 수 없는 것과 마찬가지로 2진법을 사용해 분수 값이 포함된 10진수의 값을 정확하게 저장하는 방법은 없습니다.
IEEE-754에선 가장 가까운 숫자로 반올림하는 방법을 사용해 이러한 문제를 해결합니다. 그런데 반올림 규칙을 적용하면 발생하는 미세한 정밀도 손실이 발생합니다. 우리는 그걸 MACHINE EPSILON이라고 부릅니다. 기준 epsilon의 값은 $2^{-53}$입니다.
소수점 n번째 수까지의 어림수를 구한 후 이를 문자형으로 반환해주는 메서드인 toFixed(n)를 사용해 눈으로 확인해볼 수 있습니다.
그렇기 때문에 분수 값을 포함한 10진수의 비교에는 machine EPSILON이 필요합니다.
다른 언어에서도 같은 이슈가 있습니다.
자바스크립트와 동일한 숫자 형식을 사용하기 때문에 PHP, Java, C, Perl, Ruby에서도 똑같은 결과를 얻습니다.
실제 계산이 필요한 업무를 진행하다 보면 무한 소수가 나오는 경우를 완전히 차단해야 하는 경우가 존재합니다.
평가와 같이 소수점에 상당히 민감한 정보들과 같이 점수 산정에서 소수점 하나하나가 매우 중요한 경우입니다.
사실 무한 소수를 방지하는 완벽한 방법은 없습니다. 필요할 때마다 어림수를 만드는 방법밖에 존재하지 않습니다.
toFixed 함수를 통해 어림수를 만들거나, 위와 같은 방식으로 비교를 진행하거나 10진수로 만든 후 연산을 진행한 수 다시 10의 거듭제곱으로 나누어주는 방법이 있지만, 결국 분수를 만든다는 점에서 무한소수가 다시 발생할 가능성이 존재합니다.
이러한 경우도 있습니다. 정수의 유효 자릿수는 53자리를 사용합니다. 만약 거대한 수를 저장해 53비트를 모두 사용해도 모자라다면 최소 유효 숫자가 손실됩니다. 자바스크립트는 숫자 손실이 발생해도 오류를 발생시키지 않습니다.
흥미로운 사실은 또 있습니다. 자바스크립트의 정수의 상한선은 $2^{53}$이라고 할 수 있습니다.
부호 비트를 별도로 저장한다는 사실과 fraction은 52비트로만 구성된다는 점과 53번째 비트를 제공하는 지수가 존재한다는 것입니다.
지수는 분수로 이동하여 0을 제외한 모든 53비트의 숫자를 나타낼 수 있고 0을 나타내는 특수 값을 가집니다.
이 말은 다음 뜻을 가집니다. 세 가지 구성 요소인 기호, 부호, 숫자의 분수가 정수를 나타내기 위해 함께 작동한다는 것을 의미합니다.
'JavaScript' 카테고리의 다른 글
Iteration protocol (0) | 2022.01.17 |
---|---|
Symbol (0) | 2022.01.16 |
export, import (0) | 2021.11.28 |
Prototype (0) | 2021.11.28 |
JavaScript Execution Context (0) | 2021.11.27 |
댓글