preface

In the front-end interview often encounter a classic interview question, why in JS will appear 0.1+0.2! = 0.3. As a front end we should be very familiar with this problem, for its answer must already know. But why does the JS Number type appear to produce such characteristics, and how is it stored in the computer? Start relearning Number, one of JS’s primitive types, with some questions.

How is the JS Number type stored inside the computer

Before exploring why JS Number type can appear precision loss problem, we should first understand how the computer internal store Number type value, in ecMA-262 standard can be checked

primitive value corresponding to a double-precision 64-bit binary format IEEE 754-2019 value

JS values of type Number are stored in accordance with IEEE 754 64-bit double precision floating-point format. For the 64-bit double precision floating point number defined in IEEE 754, it is composed of three parts: sign, exponent and mantissa.

Among them:

  • 1. The symbol for storing values, 0 for a positive number and 1 for a negative number;
  • Exponent bits: store binary powers and weight floating-point numbers;
  • Mantissa bit: Stores the decimal part of the binary, that is, the significant value part, representing the precision of the floating point number;

The binary offset of a floating point number

To make it easier to use unsigned integers to represent all exponential values (and to directly compare the sizes of two floating-point numbers), IEEE 754 uses Offset binary encoding, which specifies that the encoded value in the exponential field is equal to the actual encoded value plus a fixed Offset value. For 64-bit double-precision floating-point numbers, the original 2112^{11}211 (0 to 2047) is divided by half to represent negative numbers, So the fixed offset for 64-bit double-precision floating-point numbers is 211−12^{11-1}211−1-1 = 1023 (0 and 2047 represent special value processing). The fixed offset is subtracted by the exponent when calculating a floating point decimal to get the true exponent. So the actual exponents of 64-bit double-precision floating-point numbers range from -1022 to 1023, so the range of values that can be expressed in JS is [2−2013−522^{-2013-52}2−2013−52, 210232^{1023}21023] (where the value is 0 when the exponent bit -1023 and the significant binary bits are all 0).

Math.pow(2, -1074)
// 5e-324

Math.pow(2, -1075)
/ / 0
Copy the code

And IEEE 754 states that the first digit of a floating-point number in protocol form is always 1, that is, the representation of a floating-point number in protocol form is 1. Xx… XXX. Where 1 is the hidden bit and is not stored in the computer, only XX is stored inside the computer… The significant number in XXX. Therefore, for the Number type in JS, there can be 53 binary bits of significant digits inside the computer, which also means that JS can accurately represent any integer between 2−532^{-53}2−53 to 2532^{53}253

Math.pow(2.53) 
/ / 9007199254740992

Math.pow(2.53) + 1
/ / 9007199254740992
Copy the code

JS special value of type Number

JS contains +0, -0, +Infinity, -infinity, and NaN. According to IEEE 754 (for 64-bit double precision floating point numbers),

  • When the exponent is 0 and the mantissa binary representation is all 0, it represents 0 value;
  • When the exponent is 2047 and the mantissa binary representation is all 0, it represents the Infinity value.
  • When the exponent is 2047 and the binary representation of mantissa is not all 0, it is a NaN value (in JS NaN value is 9007199254740990, that is (2532^{53}253-2));

In JS +0 and -0 are usually the same, they are only the sign bits of 64-bit floating point numbers different, only when they are used as denominators return different values.

1/+0= = =1/-0
// false

1/+0
// Infinity

1/-0
// -Infinity

Copy the code

JS Infinity means Infinity, which generally occurs when a non-zero value is divided by 0 and the value is too large to be within the range of JS values such as

Math.pow(2.1024) 
// Infinity
Copy the code

NaN, on the other hand, is usually a bit rather than a Number, but NaN is still of type Number, which can be determined by number.isnan ()

Rounding of floating point numbers

The JS Number type is stored in the computer, so we can know that we usually write decimal numeric code such as const a = 106.3 is stored in the computer’s internal binary form. Decimal integers can be converted perfectly to binary for storage, but for decimal decimals, most conversion to binary forms a repeating decimal, as is known as PI 3.1415926… In general, computers cannot use large amounts of memory to store decimals that are not important in everyday life. Therefore, the computer will adopt the strategy of discarding excess bits for storage, which will lead to the loss of accuracy, and when converting to decimal again, 0.1 + 0.2! = 0.3

The solution

In ES6, a Number of attributes and methods were added to the Number type. One of these attributes is number. EPSILON, which represents the difference between 1 and the smallest floating point Number greater than 1, equivalent to 1.0… (51 0)… The value of 1-1 is equivalent to the value of 2−522^{-52}2−52, so we can compare it with number. EPSILON as a reference value, and consider them equal when the difference between the left and right sides of the equal sign is less than number. EPSILON, for example

function isEqual(){ 
    return Math.abs(x - y) < Number.EPSILON 
}
isEqual(0.1+0.2 , 0.3)
// true
Copy the code

conclusion

I gained a lot by searching for materials in my spare time in the past two days and re-understanding the basic knowledge of JS. The Number type, which I thought was very simple before, turns out to have a lot of knowledge waiting to be explored and learned.

If there are any mistakes or omissions in the article, please correct them, and finally 🙏 thank you for your reading.