Why 0.1 + 0.2 === 0.30000000000000004, 0.3-0.2 === 0.099999999999998?

preface

In recent business development, the author came across a Bug related to JavaScript floating point numbers.

Here is a brief description of the background: in the withdrawal of relevant business, the value shown to the user in units of units will be converted into the value in units. For example, 0.57 yuan translates into 57 points.

The transformation method is simple

OnInput (e) {letvalue = e.target.value; // Restrict the input of characters other than digits and decimal pointsif(! / ^ \ d * \ \ d {0, 2} {0, 2} $/. The test (value)) {value. The value = replace (/ / ^ \ d/g,' ')
            .replace(/^\./g, ' ')
            .replace(/\.{2,}/g, '. 'Replace (/^(.*\.. {2}). * $/,'$1'); } / /... This.setdata ({cash: +value * 100})}Copy the code

This seemingly innocuous code is submitted to the background, but the interface returns the parameter values incorrectly formatted.

At first, I suspected that the regular expression was missing, but after testing it, I tried the value of 0.57 entered by the user, only to find that the calculated value was unexpected: 0.57 * 100 === 56.99999999999999

Front-end development students more or less should have seen the classic problem of 0.1 + 0.2 === 0.30000000000000004. The author also read the article with a curious attitude and felt ashamed to say that he would never have developed a 0.1 + 0.2 business anyway, and he only had a glimpse of why this was the case.

Now stepped on the pit, can only say that he jumped into the pit dug in those days, that today will fill the pit.

This article will cover the following questions, if you are familiar with them, you can skip it.

  1. Why 0.1 + 0.2 === 0.30000000000000004
  2. Why does 0.57 * 100 === 56.999999999999
  3. Why does 0.57 * 1000 === = 570

Why 0.1 + 0.2 === 0.30000000000000004?

The answer to this question always comes around to JavaScript’s most basic and core floating-point format storage. In JS, both integers and decimals are Number types. Its implementation follows IEEE 754. It is a standard Double precision floating point Number, which is represented by a fixed 64-bit.

You might not want to see this. Ok, ok, that later again, here is a simple explanation in plain English, detailed content in the back of the article to read.

In fact, all the numbers in JS are converted to binary storage, because the number storage is limited to 64 bits, but in the real world, the number is infinite, so there will be numbers beyond the storage range. Numbers outside this range will lose accuracy when stored.

At the same time, we all know that when an integer is converted from decimal to binary, it is divided by two to eliminate the remainder, which is divisible! But what we may not know is that decimal conversion to binary is computed by multiplying the decimal part by 2, taking the whole part until the decimal part is 0, and if it is never zero, rounding the 0 to 1 when the last bit of precision is exceeded.

/* 0.1 to binary calculation process */ 0.1 * 2 = 0.2 > 0 0.2 * 2 = 0.4 > 0 0.4 * 2 = 0.8 > 0 0.8 * 2 = 1.6 > 1 0.6 * 2 = 1.2 > 1 0.2 * 2 = 0.4 > 0... And then there's the loopCopy the code

Here, we can find some clues

// Use toString(2) to output base 10 as binary string 0.1.toString(2); //"0.00011001100110011001100110011001100110011001100110011001100..."0.2. The toString (2); //"0.001100110011001100110011001100110011001100110011001100110011..."// The result of binary summation is 52 bits, and the 53rd bit is rounded to 1 >"0.010011001100110011001100110011001100110011001100110011, 1"// The result is const s ="0.010011001100110011001100110011001100110011001100110100"// Use the algorithm. a = 0; s.split(' ').forEach((i, index) => { a += (+i/Math.pow(2, index+1))}); / / a > > 0.30000000000000004Copy the code

Up to here, 0.1 + 0.2 === 0.30000000000000004

The above discussion process still has some doubts

  1. Why is it that after the decimal is converted to binary, the accuracy is exceeded after 52 digits?

All of this has to do with how 64-bit double-precision floating-point numbers are stored, which we’ll leave to the end.

Why 0.57 * 100 === 56.999999999999?

Why 0.57 * 1000 === 570?

After reading the previous section, we can make some guesses of our own about multiplying decimals.

When the value 0.57 is stored, its accuracy is not very accurate, so we can use toPrecision to obtain the accuracy of decimals.

/ / 0.57 toPrecision (55)"0.5699999999999999511501869164931122213602066040039062500"

Copy the code

The author’s original idea is a bit silly, the actual value of 0.57 is 0.56999.. So 0.57 * 100 is 0.56999… * 100, that’s 56.99999999999999.

At this time, Lu asked me a question, why 0.57 * 1000 === 570 and not 569.99999… “, I can only answer “it must be the loss of accuracy.”

However, my “little eyes were full of big doubts”…

And on second thought, in fact, as we all know, computer multiplication is actually cumulative, not bitwise multiplication as we think.

/ / pseudo code (0.57) * 100 = (0.57) * (64 + 32 + 4) = 0.57 (binary) * (2 ^ 6 ^ 5 + 2 ^ 2 + 2) = 0.57 binary * 2 binary * 2 ^ ^ 6 + 0.57 + 0.57 * 5 2 ^ 2Copy the code

This is really missing due to accuracy loss, when converting binary to decimal, the result is 56.99999… the

In the same way, (0.57 * 1000) is not a simple multiplication, it is also a sum, but the final precision loss when rounded by 1, the result is 570.

To solve the problem

For most businesses, once the numerical accuracy is determined, math.round is fine. Take, for example, the BUG we encountered initially in this article

Const value = math.round (0.57 * 100);Copy the code

While we are not sure of the accuracy of floating point number operation, the general solution is to convert the decimal into an integer, after calculation, and then convert the decimal.

Here are the quotes [1]

/** * exact addition */function add(num1, num2) {
  const num1Digits = (num1.toString().split('. ') [1] | |' ').length;
  const num2Digits = (num2.toString().split('. ') [1] | |' ').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}
Copy the code

Of course, there are already mature tool libraries to use, such as math.js, BigDecimal.js, number-precision, etc. You can take your pick

Floating point number storage in IEEE754

The following is actually from a Wiki

64-bit figure for partition

Bit 0: the flag bit that is the sign bit 1-11: the exponent bit 12-63: mantissa

0.1, 0.1, for example, binary is 0.00011001100110011001100110011001100110011001100110011001100…

So, first, the number is positive, and the flag bit sign = 0

Second, we’re going to convert the decimal to scientific notation, so we have exponent minus 4, so we have exponent = 2 ^ 10-4 = 1019

1.1001100110011001100110011001100110011001100110011001100... * ^ 2-4

Because of scientific notation, the first number is always 1, so you can ignore the storage and just store the next 52 bits

If more than 52, is 53 0 to 1, the result is 100110011001100110011001100110011001100110011001101.

This is roughly what floating-point numbers with Double precision look like, which answers the above question.

That’s all for this article.

Besides: good reading, no understanding; Every understanding, and gladly forget food

reference

[1]JavaScript floating point traps and solutions


If you think the article is helpful to you, then pay attention to the [IVWEB community] public number ~