> 9007199254740992 + 1 9007199254740992 > 9007199254740992 + 2 9007199254740994
sign (1 bit) 63
|
exponent (11 bit) 62
52
|
fraction (52 bit) 51
0
|
The components work as follows: If the sign bit is 0, the number is positive, otherwise negative. Roughly, the fraction contains the digits of a number, while the exponent indicates where the point is. In the following, we’ll often use binary numbers, which is a bit unusual when it comes to floating point. Binary numbers will be marked by a prefixed percentage sign (%). While JavaScript numbers are stored in binary, the default output is decimal [1]. In the examples, we’ll normally work with that default.
Examples:
f = %101, p = 2 | Number: %1.101 × 22 = %110.1 |
f = %101, p = −2 | Number: %1.101 × 2−2 = %0.01101 |
f = 0, p = 0 | Number: %1.0 × 20 = %1 |
52 | 51 | 50 | ... | 1 | 0 | (bits) | |
p=52 | 1 | f51 | f50 | ... | f1 | f0 | |
p=51 | 0 | 1 | f51 | ... | f2 | f1 | f0=0 |
... | |||||||
p=0 | 0 | 0 | 0 | ... | 0 | 1 | f51=0, etc. |
Second, for a full 53 bits, we still need to represent zero. How to do that is explained in the next section. Note that we have the full 53 bits for the magnitude (absolute value) of the integer, as the sign is stored separately.
A few numbers in offset binary encoding:
%00000000000 0 → −1023 (lowest number) %01111111111 1023 → 0 %11111111111 2047 → 1024 (highest number) %10000000000 1024 → 1 %01111111110 1022 → −1To negate a number, you invert its bits and subtract 1.
Second, an exponent of 0 is also used to represent very small numbers (close to zero). Then the fraction has to be non-zero and, if positive, the number is computed via
%0.f × 2−1022This representation is called denormalized. The previously discussed representation is called normalized. The smallest positive (non-zero) number that can be represented in a normalized manner is
%1.0 × 2−1022The largest denormalized number is
%0.1 × 2−1022Thus, there is no hole when switching between normalized and denormalized numbers.
(−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 |
With p = e − 1023, the exponent has a range of
−1023 < p < 1024
> 0.1 + 0.2 0.30000000000000004Neither of the decimal fractions 0.1 and 0.2 can be represented precisely as a binary floating point number. However, the deviation from the actual value is usually too small to be displayed. Addition leads to that deviation becoming visible. Another example:
> 0.1 + 1 - 1 0.10000000000000009Representing 0.1 amounts to the challenge of representing the fraction . The difficult part is the denominator 10, whose prime factorization is 2 × 5. The exponent only lets you divide an integer by a power of 2, so there is no way of getting a 5 in. Compare: cannot be represented precisely as a decimal fraction. It is approximated by 0.333333...
In contrast, representing a binary fraction as a decimal fraction is always possible, you just need to collect enough twos (of which every ten has one). For example:
%0.001 = = = = = 0.125
var epsEqu = function () { // IIFE, keeps EPSILON private var EPSILON = Math.pow(2, -53); return function epsEqu(x, y) { return Math.abs(x - y) < EPSILON; }; }();The above function ensures correct results where normal comparison would be inadequate:
> 0.1 + 0.2 === 0.3 false > epsEqu(0.1+0.2, 0.3) true
> Math.pow(2, 53) 9007199254740992 > Math.pow(2, 53) - 1 9007199254740991 > Math.pow(2, 53) - 2 9007199254740990But the next integer cannot be represented:
> Math.pow(2, 53) + 1 9007199254740992A few aspects of 253 being the upper limit might be surprising. We will look at them via a series of questions. One thing to keep in mind is that the limiting resource at the high end of the integer range is the fraction; the exponent still has room to grow.
Why 53 bits? You have 53 bits available for the magnitude (excluding the sign), but the fraction comprises only 52 bits. How is that possible? As you have seen above, the exponent provides the 53rd bit: It shifts the fraction, so that all 53 bit numbers except the zero can be represented and it has a special value to represent the zero (in conjunction with a fraction of 0).
Why is the highest integer not 253−1? Normally, x bit mean that the lowest number is 0 and the highest number is 2x−1. For example, the highest 8 bit number is 255. In JavaScript, the highest fraction is indeed used for the number 253−1, but 253 can be represented, thanks to the help of the exponent – it is simply a fraction f = 0 and an exponent p = 53 (after conversion):
%1.f × 2p = %1.0 × 253 = 253
Why can numbers higher than 253 be represented? Examples:
> 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 18014398509481984253×2 works, because the exponent can be used. Each multiplication by 2 simply increments the exponent by 1 and does not affect the fraction. So multiplying by a power of 2 is not a problem as far as the maximum fraction is concerned. To see why one can add 2 to 253, but not 1, we extend the previous table with the additional bits 53 and 54 and rows for p = 53 and p = 54:
54 | 53 | 52 | 51 | 50 | ... | 2 | 1 | 0 | (bits) | |
p=54 | 1 | f51 | f50 | f49 | f48 | ... | f0 | 0 | 0 | |
p=53 | 1 | f51 | f50 | f49 | ... | f1 | f0 | 0 | ||
p=52 | 1 | f51 | f50 | ... | f2 | f1 | f0 |
Looking at the row (p=53), it should be obvious that JavaScript numbers can have bit 53 set to 1. But as the fraction f only has 52 bits, bit 0 must be zero. Hence, only even numbers x can be represented in the range 253 ≤ x < 254. In row (p=54), that spacing increases to multiples of four, in the range 254 ≤ x < 255:
> Math.pow(2, 54) 18014398509481984 > Math.pow(2, 54) + 1 18014398509481984 > Math.pow(2, 54) + 2 18014398509481984 > Math.pow(2, 54) + 3 18014398509481988 > Math.pow(2, 54) + 4 18014398509481988And so on...
> Math.sqrt(-1) NaN
> 3 / 0 Infinity > -5 / 0 -Infinity
> Math.pow(2, 2048) Infinity > -Math.pow(2, 2048) -Infinity
> Math.pow(2, -2048) 0
> 0.1 + 0.2 0.30000000000000004 > 9007199254740992 + 1 9007199254740992
Bonus: The web page “IEEE-754 Analysis” allows you to enter a number and look at its internal representation.