Javascript에서 숫자가 인코딩 되는 방법 - Lee-hyuna/33-js-concepts-kr GitHub Wiki
JavaScript의 모든 숫자는 부동 소수점(floating point)이다. 이 포스팅은 부동 소수점 숫자가 내부적으로 어떻게 binary64(64비트)으로 표현되는지 설명한다. 이 게시물을 읽은 후에는 다음 상호작용이 어떻게 일어나는지 이해하게 될 것이다.
> 9007199254740992 + 1
9007199254740992
> 9007199254740992 + 2
9007199254740994
Javascript 숫자는 모두 부동 소수점으로 IEEE 754 표준에 따라 저장된다. IEEE 754 표준은 몇 가지 포맷을 지닌다. Javascript는 binary64
나 배정밀도(double percision)
를 사용한다. 용어서 알 수 있듯이, 숫자는 64비트로 2진법으로 저장된다. 이 비트는 다음과 같이 할당된다: 분수(fraction)
는 비트 0에서 51을 차지하고, 지수(exponent)
는 비트 52에서 62를 차지하고, 부호(sign)
는 비트 63을 차지한다.
float
: 단정밀도(single precision) 32비트double
: 배정밀도(double precision) 64비트
부호(sign) | 지수(exponent) | 분수(fraction) |
---|---|---|
(1 bit) | (11 bit) | (52 bit) |
63 | 62 ~ 52 | 51 ~ 0 |
각 구성요소는 다음과 같이 작동한다. : 부호(sign) 비트가 0이면 숫자는 양수이고 그렇지 않으면 음수이다. 분수(fraction)에는 숫자의 숫자가 포함되어 있고 지수(exponent)는 점이 어디에 있는지 나타낸다. 주로 이진수를 사용하는 경우가 많고. 부동 소수점은 특이한 케이스이다. 2진수는 접두사로 백분율 부호 (%)를 가진다. 자바스크립트 번호는 2진수로 저장되지만 기본 출력은 10진수로 표현된다. 예제에서는 보통 10진수를 기준으로 설명할 것이다.
다음은 음수가 아닌 부동 소수점 수를 표현하는 한 가지 방법이다.: 유효숫자(significand or 가수(matissa)는 자연수를 포함하고 지수는 왼쪽 (음수 지수) 또는 오른쪽 (양수 지수) 점 이동해야 합니다 다음은 음수가 아닌 부동 소수점 숫자를 나타내는 방법 중 하나다. : 유효숫자(significand or 가수(matissa)는 자연수로서 숫자를 포함하고 있고, 지수(exponent)는 왼쪽(음수 지수) 또는 오른쪽(양수 지수)까지의 자릿수를 지정한다. Javascript 숫자는 유리수를 유효숫자로 사용한다.: 1. f는 52비트 분수이고, 부호를 무시하고 숫자는 유효숫자고 2p를 곱한 것이다. 여기서 p는 지수(나중에 설명할 예정이지만 변환된 지수)이다.
예제:
f = %101, p = 2 | Number: %1.101 × 2 2 = %110.1 |
f = %101, p = −2 | Number: %1.101 × 2 −2 = %0.01101 |
f = 0, p = 0 | Number: %1.0 × 2 0 = %1 |
인코딩된 정수가 몇 비트나 될까?
인코딩이 정수에 대해 몇 비트를 제공하는가? 유효숫자(significand)는 53 자리 숫자르 가지고 있다. 하나는 포인트 앞에, 52는 포인트 뒤에 있다. p = 52를 사용하면 53비트 자연수다. 문제는 가장 높은 비트는 항상 1이라는 점이다. 즉, 우리가 자유롭게 모든 비트를 가질 수 없다. 1은 두 단계로 거쳐 그 제한을 제거한다. 첫째, 가장 높은 비트가 0인 53 비트 번호가 필요한 경우 1이 뒤 따르면 p = 51을 설정한다. 분수의 가장 낮은 비트는 점 다음에 첫 번째 숫자가되고 정수에 대해 0이 된다. 그리고 p = 0과 f = 0이 될 때까지 숫자 1을 인코딩한다.
둘째, 여전히 전체 53비트 인 경우 0을 표현해야한다. 다음 절에서 어떻게 해야 하는지 설명할 예정이다. 부호가 별도로 저장되므로 정수의 크기(절대 값)에 대한 전체 53 비트가 있음을 유의 하도록.
오프셋 바이너리 인코딩에 있는 몇 개의 숫자:
%00000000000 0 → −1023 (lowest number)
%01111111111 1023 → 0
%11111111111 2047 → 1024 (highest number)
%10000000000 1024 → 1
%01111111110 1022 → −1
지수는 11 비트 길이로, 가장 낮은 값이 0이고 가장 높은 값은 2047 (211 -1)입니다. 음수 지수를 지원하기 위해 이른바 오프셋 바이너리(1023은 0이고 모든 낮은 숫자는 음수이며 높은 숫자는 모두 양수) 인코딩이 사용된다. 이것은 지수에서 1023을 뺀 것을 일반 숫자로 변환한다는 것을 의미한다. 따라서 이전에 사용 된 변수 p 는 e-1023과 같고 유효숫자는 2e-1023을 곱한다.
가장 낮은 값 (0)과 가장 높은 값 (2047), 두개의 지수 값이 예약되어 있다. 2047의 지수는 무한대 및 NaN (숫자가 아닌) 값에 사용된다. IEEE 754 표준에는 많은 NaN 값이 있지만 Javascript는 모두 이를 하나의 NaN으로 나타낸다. 지수 0는 두 가지 용량에서 사용된다. 먼저 분수(fraction)가 0이면 전체 숫자는 0입니다. 기호가 별도로 저장되므로 우리는 -0과 +0을 모두 가진다.
둘째, 0의 지수는 매우 작은 수 (0에 가깝다)를 나타내는 데에도 사용된다. 그런 다음 분수는 0이 아니어야하며 양수인 경우 숫자는 다음과 같이 계산된다.
%0.f × 2−1022
이 표현을 비정규화라고 한다. 앞에서 논의된 표현을 정규화라고 한다. 정규화된 방식으로 나타낼 수 있는 최소 양의(비영점) 수는
%1.0 × 2−1022
가장 큰 비정규화 숫자는
%0.1 × 2−1022
그렇기에 정규화된 숫자와 비정규화된 숫자 사이를 전환할 때 빈틈이 없다.
(−1)s × %1.f × 2e−1023 | normalized, 0 < e < 2047 |
(−1)s × %0.f × 2e−1022 | denormalized, e = 0, f > 0 |
(−1))s × 0 | e = 0, f = 0 |
NaN | e = 2047, f > 0 |
(−1)s × ∞ (infinity) | e = 2047, f = 0 |
다음 결과에 표시된 것처럼 모든 소수를 JavaScript로 정확하게 표현할 수 있는 것은 아니다.
> 0.1 + 0.2
0.30000000000000004
십진수 소수점 0.1과 0.2도 정확하게 이진 부동 소수점 수로 표현 될 수 없다. 하지만 일반적으로 실제 값과의 편차가 너무 작아 표시 할 수 없다. 추가하면 이러한 편차가 눈에 띄게 된다. 다른 예제:
> 0.1 + 1 - 1
0.10000000000000009
0.1을 나타내는 것은 분수 110(10/100)을 나타내는 도전에 해당한다.어려운 부분은 분모 10 이다.이 분모의 인수 분해는 2 x 5입니다. 지수는 정수를 2의 거듭 제곱으로 나눌 수 있지만 5를 얻을 수 있는 방법이 없다. 비교 : 13은 소수 자릿수로 정확하게 표현될 수 없다. 0.333333...로 근사된다.
대조적으로, 소수 분수로 이진 분수를 나타내는 것은 항상 가능하고, 충분한 2를 수집해야한다. (10 개마다 1 개씩 있음). 예를 들면 :
%0.001 = 18 = 12 × 2 × 2 = 5 × 5 × 5(2×5) × (2×5) × (2×5) = 12510 × 10 × 10 = 0.125
따라서 분수 값을 갖는 소수점 입력을 사용하여 작업 할 때 직접 비교하면 안된다. 대신 반올림 오류에 대한 상한선을 고려하라. 이러한 상한을 machine epsilon 이라고 한다. 배정밀도(double precision)에 대한 표준 엡실론 값은 2-53이다.
```
var epsEqu = function () { // IIFE, keeps EPSILON private
var EPSILON = Math.pow(2, -53);
return function epsEqu(x, y) {
return Math.abs(x - y) < EPSILON;
};
}();
```
위의 함수는 정상 비교가 부적절한 경우 정확한 결과를 보장한다.
```
> 0.1 + 0.2 === 0.3
false
> epsEqu (0.1 + 0.2, 0.3)
true
```
"x가 최대 정수"라고 하면 어떤 의미일까? 즉, 0 ≤ n ≤ x 범위의 모든 정수 n을 나타낼 수 있고, x보다 큰 정수는 같지 않다. 253번은 그 계산서(?)에 들어맞는다. 이전 모든 숫자는 다음과 같이 나타낼 수 있다.:
> Math.pow(2, 53)
9007199254740992
> Math.pow(2, 53) - 1
9007199254740991
> Math.pow(2, 53) - 2
9007199254740990
그러나 다음 정수는 나타낼 수 없다.
> Math.pow(2, 53) + 1
9007199254740992
상한치인 253의 몇 가지 측면은 놀랍다. 일련의 질문을 통해 그것들을 알아볼 것이다. 한 가지 명심해야할 점은 정수 범위의 하이 엔드에서 한계 자원이 분수라는 것이다. 지수는 여전히 성장할 여지가 있다.
왜 53 비트인가? 크기(부호 제외)에 대해 53비트를 사용할 수 있지만, 분수는 52비트로만 구성된다. 어떻게 그것이 가능할까? 위에서 본 바와 같이, 지수기는 53번째 비트를 제공한다: 이는 분수를 변화시켜 0을 제외한 53개의 비트 수를 모두 나타낼 수 있고 0을 나타내는 특별한 값을 갖는다 (분율 0과 함께).
왜 가장 높은 정수가 253-1이 아닌가? 일반적으로 x 비트는 가장 낮은 숫자가 0이고 가장 높은 숫자는 2x-1임을 의미한다. 예를 들어, 가장 높은 8비트 번호는 255이다. Javascript에서 가장 높은 분율은 실제로 숫자 253-1에 사용되지만, 지수 f = 0, 지수 p = 53(변환 후)의 도움 덕분에 253을 나타낼 수 있다.
%1.f × 2p = %1.0 × 253 = 253
왜 253 보다 높은 숫자를 나타낼 수 있는가?
> Math.pow(2, 53)
9007199254740992
> Math.pow(2, 53) + 1 // not OK
9007199254740992
> Math.pow(2, 53) + 2 // OK
9007199254740994
> Math.pow(2, 53) * 2 // OK
18014398509481984
253×2는 지수를 사용할 수 있기 때문에 작동한다. 2에 의한 각 곱셈은 단순히 지수 1을 증가시킬 뿐 분율에 영향을 미치지 않는다. 따라서 2의 제곱으로 곱하는 것은 최대 분율에 관한 한 문제가 아니다. 1이 아닌 2를 253에 추가할 수 있는 이유를 확인하려면, 이전 테이블을 추가 비트 53 및 54와 p = 53 및 p = 54의 행으로 확장하면 된다.
행 (p = 53)을 보면 Javascript 숫자가 비트 53을 1로 설정할 수 있음을 분명히 알 수 있다. 그러나 분수 f는 52 비트만 가지므로 비트 0은 0이어야 한다. 따라서 짝수 x만 253 ≤ x < 254 범위로 나타낼 수 있다. 행 (p = 54)에서 그 간격은 254 ≤ x < 255 범위에서 4의 배수로 증가한다.
> Math.pow (2, 54)
18,014,398,509,481,984
> Math.pow (2, 54) + 1
18,014,398,509,481,984
> Math.pow (2, 54) + 2
18,014,398,509,481,984
> Math.pow (2, 54) + (3)
18,014,398,509,481,988
> Math.pow (2, 54 ) + 4
18014398509481988
IEEE 754 에는 은 정확한 값을 계산할 수 없는 다섯 가지 예외(exception)
가 존재한다.
-
Invalid: 잘못된 작업이 수행됨. 예를 들어 음수의 제곱근을 계산하는 경우, NaN을 반환한다.
> Math.sqrt(-1) NaN
-
** 0으로 나누기**: 음의 무한대나 양의 무한대를 반환한다.
> 3 / 0 Infinity > -5 / 0 -Infinity
-
Overflow: 결과가 너무 커서 표현할 수 없다. 이는 지수가 너무 높다는 것을 의미한다. ( p ≥ 1024). 부호에 따라 양수 및 음수 오버플로가 있고, 플러스 또는 마이너스 무한대를 반환한다.
> Math.pow(2, 2048) Infinity > -Math.pow(2, 2048) -Infinity
-
Underflow: 결과가 너무 작아서 표현할 수 없다. 이는 지수가 너무 낮다는 것을 의미한다.( p ≤ -1023). 비정규화 된 값 또는 0을 반환한다.
> Math.pow(2, -2048) 0
-
부정확함(Inexact): 연산이 부정확 한 결과를 생성했다. 분수가 유지 될 유효숫자가 너무 많다. 반올림된 결과를 반환한다.
> 0.1 + 0.2 0.30000000000000004 > 9007199254740992 + 1 9007199254740992
3번과 4번은 지수에 관한 것이고, 5번은 분수에 관한 것이다. 3번과 5번의 차이는 매우 미묘하다. 5번에 주어진 두 번째 예제는 분수의 상한선을 초과했다. (정수 계산에서 오버플로우 된다). 그러나 지수의 상한선을 초과하는 것만 IEEE 754에서 오버플로라고 한다.
이 포스팅에서 JavaScript가 부동 소수점 숫자를 64 비트로 맞추는 방법을 살펴봤다. 이것은 IEEE 754 표준의 배정밀도에 따라 동작한다. 숫자가 표시되는 방식 때문에 Javascript는 분모의 인수 분해에 2 이외의 숫자가 포함 된 소수 부분을 정확하게 표현할 수 없다는 것을 잊어 버리는 경향이 있다. 예를 들어 0.5 (12)을 표현할 수 있고, 0.6 (35)은 표현 할 수 없습니다. 또한 숫자의 부호, 지수, 분수가 정수를 나타내기 위해 함께 작용한다는 것을 잊어버리는 경향이 있다. 결론적으로 Math.pow (2, 53) + 2 는 표현할 수 있지만 Math.pow (2, 53) + 1 은 표현할 수 없다.
보너스 : " IEEE-754 Analysis " 웹 페이지 에서는 숫자를 입력하고 내부 표현을 볼 수 있다.
이 게시물의 출처 :
- Steve Hollasch의 " IEEE Standard 754 Floating-Point "
- MATLAB 문서의 " 데이터 유형 및 크기 조정 (고정 소수점 블록 집합) "
- Wikipedia의 " IEEE 754-2008 "
이 게시물은 다음을 포함하는 자바 스크립트 번호 시리즈의 일부이다
- 내가 수학을 공부하는건지 자바스크립트를 공부하는건지 헷갈렸다.
- exponent, fraction, significand 등 수학 용어들이 어려웠다.