Inaccurate result in broken numbers calculation

Asked

Viewed 6,437 times

39

Problem

Test there in the consoles of your browsers:

1067.11-1000 = 67.1099999999999

The right thing would be 67.11

  1. Could someone explain this to me?
  2. And how do I fix it?
  • 4

    The added answers are already quite complete, so I won’t post one more. But if you want to read more about it and understand why comma operations can be "tricky" and should be used with care, I suggest this article: http://hal.archives-ouvertes.fr/docs/00/28/14/PDF/floating-point-article.pdf

  • I made a reply showing exactly what is happening in the computer memory, to make it clear what is happening. I hope it helps!

  • If you want even more details, I could also show how the conversion is done between double/string... I think I just needed this to close the answer I gave.

  • @Miguelangelo This conversion could be shown here: https://answall.com/q/264529/3082

3 answers

58


Short answer

This occurs because of an inaccuracy in converting the value 1067.11 to the representation of the number in memory... this inaccuracy, which is revealed to the subtract the value 1000.

Long answer

Factors of inaccuracy

Numerical inaccuracies occur by a variety of factors, arising from the way in which such numbers are represented by the computer.

Numbers with floating point, in modern systems are usually represented in accordance with the standard IEEE 754 native way. Javascript specifically, uses the double to represent all numbers.

In the case of double of the standard IEEE 754, inaccuracies are caused by two factors:

  • Amount of space to represent the number is finite. In the current case, this limitation is not what causes the imprecision.

  • Exponent numerical basis is 2, which cannot be aligned with the base of the original number 1067.11 which is at base 10. This is the cause of inaccuracy in the current case.

To better understand we have to see how exactly the type is represented double.

Components of a double

The double of the standard IEEE 754 is formed as follows:

  • signal: 1 bit for signal

  • exponent: 11 bit integer, to indicate the value of the displaced exponent 1023 units, or one of the two values with special meanings: 0x000 for subnormal and zero values; 0x7FF to represent infinity and Nan. (i.e. 2x - 1023, where x is the whole value of the field)

  • mantissa: 52 bits, for normal exponent values, represents a value rational ranging from 1.0 inclusive to 2.0 exclusive, mathematically [1, 2[. For the exponent value 0x000, then represents subnormal values (i.e. smaller than the smallest representative normal value) or zero; for the value of exponent 0x7FF represents infinity if it is 0, or Nan if it is different from 0.

Why inaccuracy appears only after subtracting 1000

Although it doesn’t seem so, number 1067.11 cannot be represented exactly by javascript. So how can javascript convert that number back to string, just like "1067.11"?

alert(1067.11); // vai mostrar "1067.11", como é possível então?

This is because the javascript implementation is smart... at the time of convert to string, shows the value shorter which would be representable by this value of double.

This happens because a single value of double results from several conversions of string for double:

1067.1100000000000000000001
1067.11000000000000001
1067.11000000000001
1067.11
1067.10999999999978626875
1067.1099999999997862687453
1067.10999999999978626874509
1067.1099999999998999553285190717

When writing any of the above numbers on Chrome console, the result is 1067.11... because this is the shortest string.

But the truth is you’re being duped!

  • The exact value of the string "1067.11" converted to double is 1067.1099999999998999553285190717.

  • The exact value minus 1000 is 67.1099999999998999553285190717.

  • The shortest value representable by the previous double is 67.1099999999999.

  • Completion:

                              1067.11 - 1000 == 67.1099999999999
                                é **EXATAMENTE** o mesmo que
    1067.1099999999998999553285190717 - 1000 == 67.1099999999998999553285190717  
    

That is to say:

    O que realmente acontece na memória  =>    O que é mostrado para você
    1067.1099999999998999553285190717    =>    1067.11
  - 1000                                 =>  - 1000
    ---------------------------------          ------------------
      67.1099999999998999553285190717    =>      67.1099999999999

References

My sources of information and learning:

27

Source: http://www.guj.com.br/8905-problemas-no-calculo-com-javascript

The reason for this is this:.

The computer does not work well with decimals. As you know, the data is represented internally in binary format.

The number 4, for example, is represented by the computer thus:

100 (onde 1x2^2 + 0x2^1 + 0x2^0 = 4)

How is a number with decimal places represented? The number becomes inaccurate because, after the comma, each connected bit represents 2 -n, where n is the number of the house counted from right to left, starting from 1. Thus, the number 2.5 would be represented in binary by:

2 = 10 
0,5 = 0.1
2,5 = 10.1

Now, consider the effort to write a fraction that is not multiple of 2, like 0.3, using powers of 2:

The first power we would use could be 1/4, which is 0.25. To improve accuracy, we can add 1/32, which is 0.03125. We would then get, 0.28125. What if we wanted to increase the accuracy even more? You’d add up to 1/64, which is 0.015625, and you’d get 0.296875... Our number already looks like this: 0.3 = 0.010011

We could continue adding multiple divisors of 2 larger and larger, but we would hardly reach 0.3. This asymptotic behavior is extremely inconvenient and, for a base like 2, extremely frequent.

With this, we see that the base system 2 is very poor to represent numbers that cannot be obtained by fractions of 2. And from this comes all this imprecision. What the computer does is increase (a lot) the number of bits when accounts involving floats are done. This attenuates the problem, but does not solve it. Small programming tips (like multiplying before splitting, when possible) also help.

However, it is often not enough. Java (but not Javascript) provides classes (Bigdecimal and Biginteger) for you to manipulate arbitrary precision numbers. In this case, the calculations are done indirectly, and are much slower compared to the primitive way. But still, they break a gallon.

This explains their odd number. It’s not a Javascript bug, it’s the way computers work. The same problem will repeat itself in all programming languages.

  • 2

    Nesse caso, os cálculos são feitos por software(...) That sounds so weird.

  • 5

    Good, this question and answer were missing from the site. I just think it would be worth mentioning some more "bureaucratic" issues, such as the use of the IEEE 754 standard in js.

  • One more thing: the answer would be more accurate if you indicated that in Javascript, there is a single numeric type (Number). This type stores values as floating points. It stores not integer values, but power values. Therefore, any calculation is based on approximations. Bonus if you cite the functions that allow you to round up results like the one in the example to force the nearest integer to be used.

  • An excellent reference on the subject for those who read English: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

  • 3

    "The same problem will repeat itself in all programming languages." I disagree with your position. This is a question of how each language treats numerical data and not a problem of how the computer stores numbers and stuff and stuff.

  • @Edgarmunizberlinck this is a problem of how the computer stores numbers, not the language. It is mathematically impossible to represent all real numbers with binary numbers.

  • @carloscinelli still does not justify the argument of "all languages". Java Bigdecimal for example has no loss of precision. Anyway, precision problems usually occur when we have numbers on different scales of accuracy, so it is not a computer problem but lack of modesty of the developer when performing its operations.

  • 1

    @Edgarmunizberlinck the need to create a library/packet of arbitrary precision stems from the fact that computers store information in binary form, it is not something of logic or syntax or the language itself. With these libraries you increase the accuracy of the real numbers on computers, but you can’t represent them exactly ever.

  • 1

    @Edgarmunizberlinck Actually, the Java Bigdecimal varies in behavior. If you use the constructor that receives a double it also operates internally in a "wrong" way, itself javadoc guides this. The same occurs in the method Bigdecimal#valueOf(double).

Show 4 more comments

14

I think the first part of the question is already well answered.. :)

As for the second, in Javascript you can format your number in fixed-point using the method Number.prototype.toFixed()

The method receives a parameter indicating the number of digits:

(1067.11-1000).toFixed(2);

in this case, the result will be the string "67.11".

To explicitly work with it again as a number:

resultado = Number( (1067.11 - 1000).toFixed(2) );
  • Good, it worked out ;)

  • 1

    Everyone explained, but the only one who solved it was you, thanks

Browser other questions tagged

You are not signed in. Login or sign up in order to post.