Writing in the front

Today, when I was reading advanced Programming in JavaScript, I noticed that the book specifically mentioned the calculation error of such a floating point number as 0.1+0.2=0.30000000000000004, which WAS very interesting. I don’t know much about floating point numbers in my work. Recently, my team members have also encountered this problem. I am going to summarize this seemingly simple basic type of Number, but it is not simple in fact. The purpose of this blog post is to learn about floating point numbers from this strange calculation.

Two established facts

  1. The absolute value range of the number can be expressed in JS is 5e-324 ~ 1.7976931348623157e+308, which can be passedNumber.MAX_VALUEandNumber.MIN_VALUETo get confirmation
  2. The maximum range of safe integers that can be expressed in JS is -9007199254740991 to 9007199254740991, which can be passedNumber.MIN_SAFE_INTEGERandNumber.MAX_SAFE_INTEGERTo prove

Two problems

  1. There is a problem of accuracy loss in the four operations, such as:01 + 0.2 //0.30000000000000004
  2. Operations that exceed the maximum safe integer are unsafe, such as:9007199254740991 + 2 // 9007199254740992

According to?

To explain these two facts and problems, you need to know how decimals are stored in a computer:

Knowledge!!

  1. Convert this floating point number into its corresponding binary number and represent it in scientific notation
  2. This value is represented by the IEEE 754 standard as a value that will actually be stored in the computer

As we know, the Number type in JS uses a double floating-point type, which is the double type in other languages. While the double precision floating point number uses 64 bit to store, the structure diagram is as follows:

That is, a Number of type Number is represented in memory as s x m x 2^e.

According to the ES specification, the range of E is from -1074 to 971, while the maximum number m can represent is 52 1, and the minimum number m can represent is 1. Here, it should be noted:

Knowledge!!

The first significant digit of binary must be 1, so this 1 will not be stored, so one memory bit can be saved, so the mantissa part can be stored in the range of 1 ~ 2^(52+1).

That means the maximum Number that Number can represent is between 2^-1074 and 2^(53+971).

Precision loss

As mentioned earlier, the decimal is first converted to binary for storage. Let’s take a look at the binary results of 0.1 and 0.2:

(0.1) = > 10 (00011001100110011001) (1001)... 10 = 2 (0.2) > (00110011001100110011) (0011)... 2Copy the code

It can be found that 0.1 and 0.2 are both infinite loop numbers after being converted into binary. The mantissae bits mentioned above can only store a maximum of 53 significant digits, so it must be rounded off. This rule is defined in IEEE 754

0001 (1001) (1001) (1001) (1001) (1001) (1001) (1001) (1001) (1001) (1001) (1001) (1001) 101 + (0011) (0011) (0011) (0011) (0011) (0011) (0011) (0011) (0011) (0011) (0011) (0011) (0011) 01 0100 (1100) (1100) (1100) (1100) (1100) (1100) (1100) (1100) (1100) (1100) (1100) (1100) 111Copy the code

Note here that the 53 bits of storage are meant to hold 53 significant digits, so the first 0 doesn’t count, and you have to go back to the 53 significant digits.

The result is 0.30000000000000004 (try an online base conversion tool if you don’t believe me).

summary

In a word, the computer uses binary to store decimals, and most decimals are infinite loop values when converted into binary. Therefore, there is a trade-off problem, which is the loss of precision.

Maximum safe integer

Here’s an article that makes this very clear (there is a mistake in the article, which will be pointed out below).

If you are too lazy to read English, you can read my summary:

The binary numbers corresponding to the maximum secure integer 9007199254740991 are as follows:

Once all 53 significant digits have been stored, to represent a larger number, you have to add one digit to the exponent, and the mantissa has no storage space, so you have to add zeros.

As shown in the figure, if the exponent bit is 53, the number with the last mantissa bit of 0 can be accurately represented, while the number with the last mantissa bit of 1 cannot be accurately represented. The ratio of what can be represented exactly to what can’t be represented exactly is 1:1.

Similarly, when the exponent is 54, only the last two montisms with 00 can be accurately represented, that is, the ratio of accurately represented and inaccurately represented is 1:3. When the number of significant digits reaches x (x >53), the ratio of accurately represented and inaccurately represented will be 1:2 ^(x-53) -1.

As you can expect, the index grows exponentially as it gets higher and higher, so the Number of integers between number.max_safe_INTEGER and number.max_value that can be accurately represented is very rare.

I found an error in this article. The article pointed out that the number 9007199254740998 could not be accurately represented. In fact, it is. And the last two mantissa is 0.

summary

The concept of a maximum safe integer is essentially because of the way digital types are stored in computers. After the mantissa bit is not enough for zero, the integer corresponding to redundant mantissa 1 cannot be accurately represented.

conclusion

It can be found that the accuracy of JS is only 53 bits (mantissaws can only store 53 significant digits), whether the calculation result of floating point number is wrong or the calculation result of large integer is wrong. So how can we solve these two problems in our daily work?

The big solution is to use Mathjs. Look at the output of MathJS:


math.config({
    number: 'BigNumber',      
    precision: 64 
});

console.log(math.format(math.eval('0.1 + 0.2'))); // '0.3'

console.log(math.format(math.eval('0.23 * 0.34 * 0.92'))); // '0.071944'

console.log(math.format(math.eval('9007199254740991 + 2'))); // '9.007199254740993 e+15'

Copy the code

Do in case of integer overflow is very few, most of the scene is the calculation of floating point Numbers, if you don’t want to because some simple calculation is introduced into mathjs, also can oneself to achieve operation function (need to consider digital whether and when a number is expressed as scientific notation scenarios), if don’t, The library API is much cleaner, and can be used to solve the problem of calculating floating-point numbers (see the code, if the number exceeds number.max_safe_INTEGER, it will throw a warning).

The resources

  • Double precision floating point wiki
  • Who stole your accuracy
  • Here is what you need to know about JavaScript’s Number type

For more exciting content, please pay attention to netease Kaola front end Team wechat official account

Ps: A wave of advertising, Kaola.com front-end recruitment ~~~ interested poke me to send resumes