1.7976931348623157e+308, the mysterious number that is the largest number that JavaScript can represent. Today we start with this mystery number and deduce how it is calculated from the IEEE 754 standard. The mysterious numbers appearing today are 1.7976931348623157e+308, 5e-324, 9007199254740991, 2.220446049250313e-16 and 0.30000000000000004.

Number.MAX_VALUE

The JavaScript Number object stores a lot of constants. For example, if you want to add 1.7976931348623157e+308, you can open the browser Console and type number. MAX_VALUE.

For more information, see 1.7976931348623157∗103081.7976931348623157 * 10^{308}1.7976931348623157∗10308.

Today we’re going to find out where that number came from.

JavaScript uses 64-bit floating point numbers as defined by the IEEE 754 standard, also known as double precision floating point numbers. The 64-bit IEEE 754 consists of three parts:

  1. Sign bit: 1 bit
  2. Exponent bias: 11 bits
  3. Fraction: 52 bits

Let’s take a look at the exponent part. There are 11 digits in the exponent. If all of them are 1, the maximum value can be 211−1=20472^{11} -1 = 2047211−1=2047. So the range of exponents is [0, 2047]. But the exponent part has a negative number, so an offset is defined, which in 64-bit floating-point numbers is 1023 (2e−12^ E-12E −1, eEE 111111). After subtracting the offset, the range of the exponents becomes [-1023, 1024].

But all ones and all zeros have special effects, so we have fewer exponents -1023 (for all zeros) and 1024 (for all ones), and the range becomes [-1022, 1023].

Floating-point numbers with partial exponents of 1 and 0 are called normalized floating-point numbers.

We know that in scientific counting methods based on 10, for example, 1.7976931348623157∗103081.7976931348623157 * 10^{308}1.7976931348623157∗10308, the number before the decimal point must be greater than 0. For binary, the number before the decimal point must be greater than 0, and there are only 0 and 1 in the binary world, so the binary science and technology method must be 1 before the decimal point, so we can save 1 bit, 52 digits can be used to represent the number after the decimal point.

To sum up, the formula for 64-bit prescriptized floating-point numbers looks like this:


1 s i g n x ( 1. F ) 2 x 2 E 1023 -1^{sign} \times (1.F)_{2} \times 2^{E-1023}

If we want the maximum value, we should take the maximum value of the exponent part 1023, and the maximum value should be if all the mantissa are 1, so our largest number should be like this:

We substitute into the formula, where sign is 0, F is all 1, and E is 2046:


1 s i g n x ( 1. F ) 2 x 2 E 1023 -1^{sign} \times (1.F)_{2} \times 2^{E-1023}

Let’s use JavaScript to verify this value:

(2支那53 - 1) * (2支那971) / / 1.7976931348623157 e+308
Number.MAX_VALUE === (2支那53 - 1) * (2支那971) // true
Copy the code

No problem, we finally calculated the mysterious number 1.7976931348623157e+308.

I didn’t mention the sign bit, the sign bit is very simple, 0 is positive, 1 is negative.

Special values 0, Infinity, NaN

As I mentioned, there’s a special effect of having all ones or all zeros in the exponent, so let’s look at three special values.

0: indicates ±0 if all the index bits are 0 and the mantissa bits are 0

POSITIVE_INFINITY and number. NEGATIVE_INFINITY

NaN: The indices are all ones, but not all mantissa zeros, indicating a non-numeric NaN

Number.MIN_VALUE and non-specified Number

Let’s look at a relatively normal Number, 5e-324, which is the value of number.min_value:

Following the above formula for the specification of floating point numbers,


1 s i g n x ( 1. F ) 2 x 2 E 1023 -1^{sign} \times (1.F)_{2} \times 2^{E-1023}

Parameterized floating-point numbers, index part range [-1022, 1023]. The minimum value is E = 1, the index part is -1022, and the mantissa part is all 0, and the minimum value is:

Let’s use JavaScript to verify this value:

2* * (-1022) / / 2.2250738585072014 e-308
Number.MIN_VALUE < 2* * (-1022) // true
Copy the code

Obviously, the minimum value 2.2250738585072014E-308 is much larger than 5E-324, and we cannot deduce 5E-324 from the known information, because IEEE 754 also defines a special type, The denormalized number is a class of numbers in which all the exponential parts are 0 and not all the mantissa parts are 0.

It is important to note that the offset is 1 less than the offset of the specification number in the non-specification number. The 64-bit non-specification floating point offset is 1023−1= 10221023-1 =10221023 −1=1022

The formula is as follows:


1 s i g n x ( 0. F ) 2 x 2 E 1022 -1^{sign} \times (0.F)_{2} \times 2^{E-1022}

Since all the exponential parts are 0 and E is 0, the exponential part is -1022, and the above formula can be simplified as:


1 s i g n x ( 0. F ) 2 x 2 1022 -1^{sign} \times (0.F)_{2} \times 2^{-1022}

As can be seen from the formula, we can use non-specified numbers to represent numbers closer to 0. So let’s look at the minimum: the exponent is always -1022. If you want to get the minimum, only one 1 at the end of the mantissa part is the smallest, as shown below:

Let’s plug in the formula

Verify this value with JavaScript again:

2* * (-1074) // 5e-324
Number.MIN_VALUE === 2* * (-1074) // true
Copy the code

Finally, the seemingly normal 5E-324 was deduced from a less normal formula.

summary

Starting from the idea of 1.7976931348623157e+308, the derivation of Number.MAX_VALUE and Number.MIN_VALUE is summarized as follows:

We can classify 64-bit floating point numbers into three categories:

1. Special values

  • 0: indicates ±0 if all the index bits are 0 and the mantissa bits are 0
  • ∞ : index all 1, mantissa all 0, it represents ±∞
  • NaN: The indices are all ones, but not all mantissa zeros, indicating a non-numeric NaN

2. Floating point numbers in protocol form

The index bits are not all 0 and not all 1, and the offset is 1023 and the index range is [-1022, 1023].


1 s i g n x ( 1. F ) 2 x 2 E 1023 -1^{sign} \times (1.F)_{2} \times 2^{E-1023}

3. Unspecified floating-point numbers

The offset is 1022, and the exponent part is only -1022


1 s i g n x ( 0. F ) 2 x 2 1022 -1^{sign} \times (0.F)_{2} \times 2^{-1022}

Who else

There are several other mysterious numbers that we can deduce with the formula above, so let’s look at them one by one:

MAX_SAFE_INTEGER Specifies the maximum safe integer

MAX_SAFE_INTEGER: 9007199254740991 MAX_SAFE_INTEGER: 9007199254740991 MAX_SAFE_INTEGER: 9007199254740991

Let’s verify this with JavaScript

2支那53 - 1 / / 9007199254740991
Number.MAX_SAFE_INTEGER === 2支那53 - 1 // true
Copy the code

No problem, the mysterious number 9007199254740991 is 253−12^{53} -1253−1

To see why this Number is the maximum safe integer, because if it is larger than this Number, the mantras are already 1’s and only the exponent can be increased, so integers larger than number.max_safe_INTEGER are:

MAX_SAFE_INTEGER is twice the value of number. MAX_SAFE_INTEGER, so the maximum safe integer must be 9007199254740991

MIN_SAFE_INTEGER is -9007199254740991. This is easy. The sign bit changes to 1.

Number.MIN_SAFE_INTEGER === - Number.MAX_SAFE_INTEGER // true
Copy the code

Minimum precision Number.EPSILON

EPSILON, 2.220446049250313E-16.

The number. EPSILON property represents the difference between 1 and the smallest floating point Number Number can represent greater than 1. The smallest floating point number that can be represented greater than 1 looks like this:

So by definition, number. EPSILON is:

Verify this with JavaScript:

2* * -52 / / 2.220446049250313 e-16
Number.EPSILON === 2* * -52 // true
Copy the code

E-16 = 2−522^{-52}2−52

Back to the classic question “Why is 0.1 + 0.2 equal to 0.30000000000000004?”

Decimal decimal to binary

First, let’s review the decimal conversion to base 2 method:

0.1 Converting binary:

0.2 Converting binary:

As you can see, 0.1 and 0.2 are both infinite repeating decimals converted to 64-bit floating point numbers.

Storage of 0.1 in 64-bit floating point numbers

Using (1019).tostring (2), we can calculate the binary of 1019 as 1111111011:

There are 10 digits in total. The index 01111111011 is obtained by adding 0 to the head:

Let’s look at the mantissa:

1, 0111 loop, the 52nd digit is 1, but it should be noted that the 53rd digit is still 1. Using the ES2021 numerical separator) 1 _0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_010:

Thus, 0.1 is stored on 64-bit floating-point numbers as follows:

Storage of 0.2 in 64-bit floating point numbers

Using (1020).tostring (2), we can calculate the binary of 1020 as 1111111100:

There are 10 digits in total. The index 01111111100 is obtained by adding 0 to the head:

The mantissa part is exactly the same as 0.1, and the mantissa part is 1_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_010. So 0.2 is stored on 64-bit floating-point numbers as follows:

Floating point addition

Now we need to add the two numbers, but the exponents do not agree, there is no way to add them directly, we need to convert, this conversion brings a second loss of accuracy:

In this case, the index bit of 0.1 needs to be adjusted to 1020, so the mantras need to be moved right. Note that the 1 before the decimal point of the specified number also needs to be moved right. Become a tail part is a 11 _0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_01:

Now that the exponents are the same, we add the mantissa:

(0b1_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_010 
+ 0b11_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_01
).toString(2)
Copy the code

Results 10110011001100110011001100110011001100110011001100111, a total of 53. The default value to the left of the decimal point is 1, and now we have one more decimal place, so we have (10)2(10)_2(10)2, Can be understood as (10.0110011001100110011001100110011001100110011001100111) 2 (10.0110011001100110011001100110011001100110011001100111) _2 (10 2 this number. 0110011001100110011001100110011001100110011001100111).

The decimal point needs to move to the left, the index +1 becomes 1021, and the mantissa needs to be dropped by 1. Since the mantissa is 1, it needs to be advanced by 1, and substituted into the formula:


1.0011001100110011001100110011001100110011001100110100 2 1021 1023 = 10011001100110011001100110011001100110011001100110100 2 2 52 = 10011001100110011001100110011001100110011001100110100 2 54 1021-1.0011001100110011001100110011001100110011001100110100 * 2 ^ {1023} \ \ = 10011001100110011001100110011001100110011001100110100 * 2 ^ \ \ = {- 2-52} 10011001100110011001100110011001100110011001100110100 * 2 ^ {- 54}

Verify with JavaScript:

0b10011001100110011001100110011001100110011001100110100 * (2* * -54) / / 0.30000000000000004
0b10011001100110011001100110011001100110011001100110100 * (2* * -54) = = =0.1 + 0.2 // true
Copy the code

No problem. Verification is over.

The resources

  • IEEE 754 – Wikipedia, the free encyclopedia
  • IEEE 754-1985 – Wikipedia
  • In-depth understanding of IEEE754’s 64-bit double precision – spaceship blog
  • IEEE754 standard single precision (32-bit)/ double precision (64-bit) floating point decoding _do-csdn blog
  • JavaScript floating point puzzle: why isn’t 0.1 + 0.2 equal to 0.3?