preface

When going to an interview with a company in the Internet finance or e-commerce industry, it is common to encounter a question like “is 0.1+0.20.1+0.20.1+0.2 equal to 0.30.30.3? This question, to the front end person of non section of the class background is a send proposition, some know 0.1+0.20.1+0.20.1+0.2 is not equal to 0.30.30.3, but continue to ask why, cannot answer very clearly.

This column summarizes some of the ways in which 0.1+0.20.1+0.20.1+0.2 is not equal to 0.30.30.3. It is important to understand the calculation process of 0.1+0.20.1+0.20.1+0.2 before answering.

0.1+0.2 calculation process calculation process

1, decimal to binary

All computations within JS are performed in binary mode. So 0.1+0.20.1+0.20.1+0.2 requires converting 0.10.10.1 and 0.20.20.2 from decimal to binary.

  • 0.1 Binary conversion algorithm:

    0.1 x 2=0.2====== Take out the integer 0

    0.2 x 2=0.4====== Take out the integer 0

    0.4 x 2=0.8====== Take out the integer 0

    0.8 x 2=1.6====== Take out the integer 1

    0.6 x 2=1.2====== Take out the integer 1

    And then it goes on forever

    0.2 x 2=0.4====== Take out the integer 0

    0.4 x 2=0.8====== Take out the integer 0

    0.8 x 2=1.6====== Take out the integer 1

    0.6 x 2=1.2====== Take out the integer 1

    So 0.1 into binary is: 0.0001 1001 1001 1001……

  • 0.2 Algorithm for converting to binary:

    0.2 x 2=0.4====== Take out the integer 0

    0.4 x 2=0.8====== Take out the integer 0

    0.8 x 2=1.6====== Take out the integer 1

    0.6 x 2=1.2====== Take out the integer 1

    And then it goes on forever

    0.2 x 2=0.4====== Take out the integer 0

    0.4 x 2=0.8====== Take out the integer 0

    0.8 x 2=1.6====== Take out the integer 1

    0.6 x 2=1.2====== Take out the integer 1

    So 0.2 to binary is: 0.0011 0011 0011 0011……

Note here that 0.10.10.1 and 0.20.20.2 translate to binary infinity. In addition, modern browsers use the floating-point binary to store the binary, so we need to convert the above binary to the floating-point binary.

2. Convert to floating point

Floating point numbers are divided into single precision for 32-bit operating systems and double precision for 64-bit operating systems. Today’s operating systems are mostly 64-bit, so I’ll just explain how the binary is converted to the binary of a double-precision floating-point number.

A double-precision floating point number uses 1 bit to represent the sign bit, 11 bits to represent the exponent bit, and 52 bits to represent the decimal bit, as shown below:

  • Sign bit: positive number is 0, negative number is 1;

  • Exponent bit: indicates the order + offset. The order is 2e−1−12^{e-1} -12E −1−1. Eee is the number of bits of the order code. The offset is the number of digits moved when the decimal point is moved to an integer of only 111. A positive number indicates a move to the left, and a negative number indicates a move to the right.

  • Decimal place: the number after the decimal point in binary.

Next, convert 0.10.10.1 to binary 0.0001100110011001…… 0.0001, 1001, 1001, 1001… 0.0001100110011001… Binary in floating-point form.

  • The offset is −4-4−4. According to the digit calculation formula, 211−1−1−4=10192^{11-1} -4 = 1019211−1−1−4=1019. Converted to binary to 101910191019 to 111111101111111110111111111011, 11 enough to zero, finally it is concluded that refers to the number of 011111110110111111101101111111011;

  • The decimal place is 100110011001…… 1001 1001 1001… 100110011001… , because only 52 decimal places can be reserved, the 53rd place is 1, so the decimal place is carried 1.

The conversion result is as follows:

Similarly, convert 0.20.20.2 into binary 0.00110011 00110011…… 0.0011, 0011, 0011, 0011… 0.00110011 00110011… To binary in the form of floating point numbers, the conversion result is shown below:

Floating point addition

When adding floating point numbers, you need to compare whether the digits are the same. If they are the same, you need to add the decimal digits directly. If they are not the same, you need to adjust the digits to the same one first.

For ease of writing, the conversion from 0.1 to float is called 0.1, and the conversion from 0.2 to float is called 0.2.

The exponential bit of 0.1 is 101910191019 and the exponential bit of 0.2 is 102010201020. Therefore, the exponent bit of 0.1 is increased by 1, that is, the decimal point of 0.1 is moved to the left by 1, and the integer bits of the floating point number are fixed to 1, as shown below

1.1001100110011001100110011001100110011001100110011010 the original 0.11001100110011001100110011001100110011001100110011010 after moving 0.1100110011001100110011001100110011001100110011001101 will the decimal 53 to leave, because of 0 without into 1Copy the code

Causes the decimal place of 0.1 to look like this:

Now that 0.1 and 0.2 have the same exponent, add the decimal place directly.

0.1 small digital + 1001100110011001100110011001100110011001100110011010 1100110011001100110011001100110011001100110011001101 0.2 the small digital = 10110011001100110011001100110011001100110011001100111Copy the code

We will find that there is one more decimal place than 52, so we need to cut off the last decimal place, the last decimal place is 1, so we need to advance 1, as follows:

10110011001100110011001100110011001100110011001100111, 1011001100110011001100110011001100110011001100110100,Copy the code

Cut off the last of the small digital rather move the decimal point to the left one, so the index to 1, the index is the index of 0.2, 102110211021, after add 1, 102110211021, converted to binary is 011111111010111111110101111111101, Then the added floating-point number looks like this:

Floating point number converted to decimal

When the binary floating-point number is finished, the result (binary floating-point number) is converted to decimal, The conversion formula is (1) – s – 1023 ∗ ∗ 2 e (1 + ∑ I = 152 (Mi ∗ 2 – I)) {} (1) * 2 ^ ^ s} {e – 1023 * (1 + \ sum_ {I = 1} ^ {52} (M_i * 2 ^ {I})) (1) – s – 1023 ∗ ∗ 2 e (1 + ∑ I = 152 (Mi ∗ 2 – I)), S is the sign bit 0 or 1, e is the decimal value of the floating-point exponent bit, I represents the number of decimal digits from left to right, the first digit I =1i= 1I =1, MiM_iMi represents the value of each digit 0 or 1.

Convert a binary floating point number to decimal:


( 1 ) 0 2 2 ( 1 + 1 2 1 + 0 + 1 2 3 + 1 2 4 + . . . ) ({1}) ^ 0 * 2 ^ {2} * (1 + 1 * 2 + 0 + 1 * 2 ^ ^ – 1-3 + 1 * 2 ^ 4 +…).

The result is as follows:


0.3000000000000000444089209850062616169452667236328125 0.3000000000000000444089209850062616169452667236328125

Due to the precision problem, get 0.300000000000000040.300000000000000040.30000000000000004 only.

The answer

0.1+0.20.1+0.20.1+0.2 is not equal to 0.30.30.3, because two precision losses occurred in the calculation of 0.1+0.20.1+0.20.1+0.2. The first time is when 0.10.10.1 and 0.20.20.2 are converted into double precision binary floating point number, because the decimal of binary floating point number can only store 52 bits, resulting in the operation of removing the 53th digit after the decimal point if the number is 1 and the number is 0, resulting in a precision loss. After 0.10.10.1 and 0.20.20.2 were converted into binary floating point number for the second time, in the process of binary floating point number addition, the decimal digit addition resulted in one more decimal digit, and the operation that the 53th digit should be carried out if it is 1, then it will be discarded if it is 0, resulting in another precision loss. Finally, 0.1+0.20.1+0.20.1+0.2 does not equal 0.30.30.3.

expand

If you do, the interviewer may also ask, “What bugs do 0.1+0.20.1+0.20.1+0.2 not equal to 0.30.30.3 cause?”

You can answer this question by saying, “There is a BUG that will cause confusion in the display of the statistics page, and there is a similar BUG that the payment amount is less than 0.010.010.01 yuan after 300300300 yuan discount of 300.01300.01300.01.”

You might go on to ask, “How do I solve the problem that 0.1+0.20.1+0.20.1+0.2 does not equal 0.30.30.3?”

You can use math.js or toFixed() to round the results, but toFixed() has an accuracy error when rounding results in Chrome or Firefox. Math.round can be used to solve the accuracy error. For example, to keep 2.552.552.55 ∗102.55*102.55∗10 at 111 decimal places, the 2.55∗102.55*102.55∗10 gets 25.525.5%25.5. Round 25.525.5%25.5 to get 252525, and then 25÷1025÷1025÷10 to get 2.52.52.5. Math.pow can be used as a simple encapsulation of math.round (math.pow (10, m) * number)/math.pow (10, m), where number is the number to be rounded and m is the number to be reserved for decimals.