All numbers in JavaScript are floating point. This blog post explains how those floating point numbers are represented internally in 64 bit binary. Special consideration will be given to integers, so that, after reading this post, you will understand what happens in the following interaction:

> 9007199254740992 + 1 9007199254740992 > 9007199254740992 + 2 9007199254740994

JavaScript numbers

JavaScript numbers are all floating point, stored according to the IEEE 754 standard . That standard has several formats. JavaScript usesor. As the former name indicates, numbers are stored in a binary format, in 64 bits. These bits are allotted as follows: Theoccupies bits 0 to 51, theoccupies bits 52 to 62, theoccupies bit 63.

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.

The fraction

The following is one way of representing non-negative floating point numbers: The(or) contains the digits, as a natural number, the exponent specifies how many digits to the left (negative exponent) or right (positive exponent) the point should be shifted. JavaScript numbers use a rational number as the significand: 1.whereis the 52 bit fraction. Ignoring the sign, the number is the significand multiplied by 2whereis the exponent (after a transformation that will be explained later).

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

Representing integers

How many bits does the encoding give you for integers? The significand has 53 digits, one before the point, 52 after the point. With= 52, we have a 53 bit natural number. The only problem is that the highest bit is always 1. That is, we don’t have all of the bits freely at our disposal. One removes that limitation in two steps. First, if you need a 53 bit number whose highest bit is 0, followed by 1, you set= 51. The lowest bit of the fraction then becomes the first digit after the point and is 0 for integers. And so on, until you are at= 0 and= 0, which encodes the number 1.

52 51 50 ... 1 0 (bits) p=52 1 f 51 f 50 ... f 1 f 0 p=51 0 1 f 51 ... f 2 f 1 f 0 =0 ... p=0 0 0 0 ... 0 1 f 51 =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.

The exponent

The exponent is 11 bit long, meaning its lowest value is 0, its highest value is 2047 (2−1). To support negative exponents, the so-called offset binary encoding is used: 1023 is the zero, all lower numbers are negative, all higher numbers are positive. That means that you subtract 1023 from the exponent to convert it to a normal number. Therefore, the variablethat we previously used equals−1023 and the significand is multiplied by 2

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 → −1

To negate a number, you invert its bits and subtract 1.

Special exponents

NaN

Two exponent values are reserved: The lowest one (0) and the highest one (2047). An exponent of 2047 is used for infinity and NaN (not a number) values [2] . The IEEE 754 standard has many NaN values, but JavaScript all represents them as a single value. An exponent of 0 is used in two capacities. First, if the fraction is also 0 then the whole number is 0. As the sign is stored separately, we have both −0 and +0 (see [3] for details).

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−1022

%1.0 × 2−1022

%0.1 × 2−1022

Summary: exponents

(−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

This representation is called. The previously discussed representation is called. The smallest positive (non-zero) number that can be represented in a normalized manner isThe largest denormalized number isThus, there is no hole when switching between normalized and denormalized numbers.

With p = e − 1023, the exponent has a range of

−1023 < p < 1024

Decimal fractions

> 0.1 + 0.2 0.30000000000000004

> 0.1 + 1 - 1 0.10000000000000009

1 10

1 3

Not all decimal fractions can be represented precisely in JavaScript, as illustrated by the following result:Neither 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:Representing 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 = 1 8 = 1 2 × 2 × 2 = 5 × 5 × 5 (2×5) × (2×5) × (2×5) = 125 10 × 10 × 10 = 0.125

Comparing decimal fractions

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

The maximum integer

> Math.pow(2, 53) 9007199254740992 > Math.pow(2, 53) - 1 9007199254740991 > Math.pow(2, 53) - 2 9007199254740990

> Math.pow(2, 53) + 1 9007199254740992

Hence, when you work with decimal input that has fractional values, you should never compare them directly. Instead, take an upper bound for rounding errors into consideration. Such an upper bound is called a machine epsilon . The standard epsilon value for double precision is 2The above function ensures correct results where normal comparison would be inadequate:What does one mean if one says “is the maximum integer”? It means that every integerin the range 0 ≤can be represented and that the same does not hold for any integer greater than. 2fits that bill. All previous numbers can be represented:But the next integer cannot be represented:A few aspects of 2being 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 18014398509481984

×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 2, but not 1, we extend the previous table with the additional bits 53 and 54 and rows for= 53 and= 54:

54 53 52 51 50 ... 2 1 0 (bits) p=54 1 f 51 f 50 f 49 f 48 ... f 0 0 0 p=53 1 f 51 f 50 f 49 ... f 1 f 0 0 p=52 1 f 51 f 50 ... f 2 f 1 f 0

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 18014398509481988

IEEE 754 exceptions

Invalid: An invalid operation has been performed. For example, computing the square root of a negative number. Returns NaN [2]. > Math.sqrt(-1) NaN Division by zero: returns plus or minus infinity [2]. > 3 / 0 Infinity > -5 / 0 -Infinity Overflow: The result is too large to be represented. That means that the exponent is too high (p ≥ 1024). Depending on the sign, there is positive and negative overflow. Returns plus or minus infinity. > Math.pow(2, 2048) Infinity > -Math.pow(2, 2048) -Infinity Underflow: The result is too close to zero to be represented. That means that the exponent is too low (p ≤ −1023). Returns a denormalized value or zero. > Math.pow(2, -2048) 0 Inexact: An operation has produced an inexact result – there are too many significant digits for the fraction to hold. Returns a rounded result. > 0.1 + 0.2 0.30000000000000004 > 9007199254740992 + 1 9007199254740992

Conclusion

1 2

3 5

Math.pow(2, 53) + 2

Math.pow(2, 53) + 1

And so on...The IEEE 754 standard describes five, where one cannot compute a precise value:#3 and #4 are about the exponent, #5 is about the fraction. The difference between #3 and #5 is very subtle: In the second example given for #5, we are exceeding the upper limit of the fraction (which would be an overflow in integer computation). But only exceeding the upper limit of the exponent is called an overflow in IEEE 754.In this blog post, we looked at how JavaScript fits its floating point numbers into 64 bits. It does so according toin the IEEE 754 standard. Due to how numbers are displayed, one tends to forget that JavaScript cannot precisely represent a decimal fraction whose denominator’s prime factorization contains a number other than 2. For example, 0.5 () can be represented, while 0.6 () cannot. One also tends to forget that the three components sign, exponent, fraction of a number work together to represent an integer. But one is confronted with that fact whencan be represented, butcannot.

Bonus: The web page “IEEE-754 Analysis” allows you to enter a number and look at its internal representation.

Sources and related reading

Sources of this post:This post is part of a series on JavaScript numbers, which includes: