Why can not my programs do arithmetic calculations correctly?

128

Sometimes the calculations work correctly, but sometimes not:

> 0.1 + 0.1
0.2 // correcto
> 0.1 + 0.2
0.30000000000000004 // ¿qué?
> 10 * 0.67
6.7 // correcto
> 10 * 0.68
6.800000000000001 // demasiado grande
> 10 * 0.69
6.8999999999999995 // demasiado pequeño

(These examples are written in JavaScript, but the same problem exists in many programming languages)

These calculations are quite easy: I think we can all do them in a moment. Why can not my programs do arithmetic calculations correctly, and what can I do if I need more accurate results?

    
asked by Peter Olson 02.12.2015 в 07:15
source

12 answers

150

The problem comes from the limitation that computers have to represent numbers of floating point using a finite number of bits.

In this article in English there is a very good explanation Basically, to represent the number 0.1 in floating point we need an infinite number of binary digits:

Dividing 1 in 10 in binary

1/10 in binary (first 1369 digits)

If we assume that our computer is capable of storing up to 53 digits, once truncated our 0.1 is represented in binary as:

0.0001100110011001100110011001100110011001100110011001101

... that in decimal is

0.1000000000000000055511151231257827021181583404541015625

For this problem there are basically two solutions:

  • Use an exact decimal type instead of a floating point. There are languages that provide these types natively (for example, .NET has the type decimal ); for those that do not, there are usually specialized libraries for it (for example decimal.js for Javascript).
  • If the number of decimals is known in advance (for example, when dealing with monetary values), the values can be represented as integers. Thus, 123.45 € would be represented as 12345 cents. The decimal point would be added at the end, at the time of showing the value to the user.

A good site with more information on this topic: link

    
answered by 02.12.2015 в 08:43
48

Many programming languages, according to the IEE754 standard , represent numbers in a representation of floating point in the binary base.

This system is similar to scientific notation . In that system you can represent numbers like this:

  • 1.23 x 10 -5 for 0,0000123
  • 1.23 x 10 1 for 12,30
  • 1.23 x 10 8 for 123.000.000

In this system (really, in any finite system ), there are numbers that can not be represented accurately. For example, 1/3 or 543/37 or pi. We can only represent approximations. With ten significant digits:

  • 0.33333333333 x 10 0 for 1/3
  • 1,467567568 x 10 -1 for 543/37
  • 3.141592654 x 10 0 for pi

In the same way, programs that use the binary system can represent some numbers exactly:

  • 1,1101 x 2 8 for 111010000 2 (464 10 )
  • 1,1101 x 2 1 for 11,101 2 (3,625 10 )
  • 1,1101 x 2 -5 for 0.000011101 2 (0.056640625 10 )

but some numbers do not. With 16 significant bits, we can represent approximations of numbers like this:

  • 1,1001100110011001 x 2 -4 (equal to 0.09999942779541015625 10 ) for 1/10
  • 1,0101010101010101 x 2 -2 (equal to 0.33332061767578125 10 ) for 1/3
  • 1,1001001000011111 x 2 1 (equal to 3.141571044921875 10 ) for pi

Sometimes it seems that the calculations are accurate when in fact they are not, for example:

> 0.1 + 0.1
0.2

0.1 and 0.2 do not have exact representation in binary, but in this program it seems that there are no problems. This is a consequence of the algorithm for the presentation of numbers. Under the hood it is calculated like this:

> 0.100000000000000005551115123126 + 0.100000000000000005551115123126
0.200000000000000011102230246252

but they are presented as 0.1 and 0.2 because they are more or less "fairly close".

  

What can I do if I need more precise results?

There are different solutions with different technologies:

  • Some languages and libraries provide a decimal type, for example .NET, Python, and Ruby languages. You still can not represent all rational numbers (for example, 1/3) exactly, but this type is useful for representing amounts of money. For money you can also use whole numbers.
  • There are libraries in different languages if you need arbitrary precision (for example BigDecimal in Java or% % co in JavaScript).
answered by 02.12.2015 в 10:10
39

Your programs can do arithmetic calculations correctly.

For example, with the bc program:

jose@luthien ~ $ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type 'warranty'. 
0.1+0.1
.2
0.1+0.2
.3
10*0.67
6.70
10*0.68
6.80
10*0.69
6.90

With Java:

package testmatematicas;
import java.math.BigDecimal;
public class TestMatematicas {
    public static void main(String[] args) {
        BigDecimal ceroPuntoUno = new BigDecimal("0.1");
        BigDecimal suma = ceroPuntoUno.add( ceroPuntoUno );
        System.out.println( suma );
        BigDecimal ceroPuntoDos = new BigDecimal("0.2");
        suma = ceroPuntoUno.add(ceroPuntoDos);
        System.out.println( suma );
        BigDecimal diez = new BigDecimal("10");
        BigDecimal ceroPuntoSesentaYSiete = new BigDecimal("0.67");
        BigDecimal multiplicacion = diez.multiply(ceroPuntoSesentaYSiete);
        System.out.println( multiplicacion );
        BigDecimal ceroPuntoSesentaYOcho = new BigDecimal("0.68");
        multiplicacion = diez.multiply(ceroPuntoSesentaYOcho);
        System.out.println( multiplicacion );
        BigDecimal ceroPuntoSesentaYNueve = new BigDecimal("0.69");
        multiplicacion = diez.multiply(ceroPuntoSesentaYNueve);
        System.out.println( multiplicacion );
    }
}

The output is:

0.2
0.3
6.70
6.80
6.90

And, in fact, in your example the computer is not calculating the sum incorrectly. What the computer is doing is what it always does: Do what you said, STRICTLY, which does not have to be what you want it to do.

When you make 0.1+0.2 it seems that you ask him to add 0.1 to 0.2, whose result should be 0.3
But it's not like that. What you have actually told the computer to do is:
Interpret and execute the expression contained in the string "0.1 + 0.2"

Which entails the following actions:

  • Lexicographical analysis that divides the string into 3 symbols (tokens): 0.1 , + and 0.2
  • Syntactic analysis that, for example (can be of many forms), creates a syntax tree structure:

  • Semantic analysis that converts 0.1 to the nearest value that is representable with the chosen data type, in this case the implicit one. And the same with 0.2. Here is a source of error. The program will not handle the 0.1 value but the closest one that is representable in the chosen data type, which in javascript is double precision as specified in standard IEEE 754 . And in that 0.1 format it is not possible to represent it exactly, so there is a small error.

  • Execution. That adds the two values and converts the result to the nearest one that is representable . This is another source of errors.
  • Printing. Print a visual representation of the sum value obtained. It may happen that the result is correct (for example 0.125 + 0.125 = 0.25) but that fewer decimal places (0.2) are printed for the chosen printing format. In this case the computer has calculated the exact solution but has shown an approximation. And it can also happen that the result is inaccurate (for example 0.1 + 0.2 = 0.30000000000000004) but that by the chosen printing format, decimals are omitted, giving the appearance of an exact result (.3) even though the sum value stored in the computer is slightly inaccurate.

Conclusions

It is possible to make exact calculations on a computer. There are specific programs and languages for it.
In general-purpose programming languages, such as C ++, Java or Javascript, approximate calculations are made by default, for performance reasons and because it is sufficient for multiple applications. In these languages it is also possible to make exact calculations but you have to use the appropriate libraries and / or language elements.

    
answered by 03.12.2015 в 11:29
24

Perl6 understands the numbers because it deals with the numbers as fractions (ratios) ( > is a" prompt "or command prompt for the command line):

perl6
> 0.1 + 0.1
0.2
> 0.1 + 0.2
0.3
> 10 * 0.67
6.7
> 10 * 0.68
6.8
> 10 * 0.69
6.9
> (0.1 + 0.5 + 0.55 - 0.15) * 7.00001 - 2 * 0.000005
7

It does not treat 7.00001 internally as a binary number. Treat it as 700001/100000 :

> 7.00001.nude.perl.say
(700001, 100000)

> 0.0000000000000000071.nude.perl.say
(71, 10000000000000000000)

> 0.000000000000000000005.nude.perl.say
(1, 200000000000000000000)

To deal with very small or very long numbers, you should use the type FatRat (fat fraction or fat ratio):

> 0.000000000000000000000000000071.nude.perl.say
(71, 1000000000000000019884624838656)
>my $pequeño = FatRat.new(71,10**30);
0.000000000000000000000000000071
> $pequeño + 1
1.000000000000000000000000000071
> $pequeño * 10
0.00000000000000000000000000071
> my $pequeño_al_cuadrado = $pequeño*$pequeño;
0.000000000000000000000000000000000000000000000000000000005041
> 1 + $pequeño_al_cuadrado
1.000000000000000000000000000000000000000000000000000000005041

Response inspired by Ovid .

Thanks to Christoph from StackOverflow for informing me about FatRat .

It is said that there are 10 types of people: those who think like a computer (a computer) and those who think like a human. I like Perl 6 because it allows me to think more like a human.

    
answered by 04.12.2015 в 04:54
23

Why when adding my numbers, like 99.92 + 0.04, instead of giving 99.96 gives a strange result like 99.96000000000001?

  

Because internally, computers use a format ( floating point    binary ) that can not accurately represent numbers like 0.1,   0.2 or 0.3 of no way .

     

When the code is compiled or interpreted, your "0.1" is rounded to   closest number in that format, resulting in a small error   rounding even before the operation is done.

Why do computers use such a stupid system?

  

It's not stupid, just different. Decimal numbers can not   accurately represent a number like ⅓, so you have to   round to something like 0.33 ─ and do not expect 0.33 + 0.33 + 0.33 to be   equal to 1 either, right?

     

Computers use binary numbers because they are faster than   handle, and because for most operations an error in the 17th   decimal figure does not matter at all since the values with which   You were not exactly that precise anyway.

What can I do to avoid this problem?

  

That depends on the type of calculations you are doing.

     
  • If you really need your results to be added exactly,   especially when you work with money: use a data type   decimal special.
  •   
  • If it's just that you do not want to see all those decimals   extra: simply format your result by rounding to a number   Fixed decimal digits when present.
  •   
  • If you do not have a type of   decimal data, an alternative is to work with integers , e.g. do   all the calculations with money in cents. But this requires more   work and has some disadvantages.
  •   

Why do other calculations like 0.1 + 0.4 work well?

  

In this case, the result (0.5) yes can be represented in a way   exact as a floating point number, and it is possible that the errors   rounding of the starting data cancel each other ─ although not   this should be over-reliance (e.g., when those two numbers   were stored in floating point representations of different   size, rounding errors may not be canceled between them).

     

In other cases like 0.1 + 0.3, the result is not really 0.4, but   is close enough so that 0.4 is the most   short that is closer to the result than any other number of   floating point. Most languages present that number instead of   convert the real result to a decimal fraction.

If you want more information, you can go to the References .

Source: puntoflotante.org

    
answered by 29.10.2016 в 01:45
20

The problem is that using a binary base, those figures can not be represented accurately. It is as if you try to represent 2/3 in the decimal system, you can not, since you would have to repeat a 6 infinitely in the last decimal, so we use an approximate.

    
answered by 02.12.2015 в 08:31
18

Your question is a specific case of a larger issue. You can read the numerical analysis summary here . It's from a mathematical point of view. From the point of view of computer science you can read here .

But the language does matter because different languages will use different approximations to the continuous numbers, and they will round the results in a different way. Here is an example.

PS>10*0.68
6.8
PS>(10*0.68).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Double                                   System.ValueType


PS>[single]$a = 10*0.68
PS>$a
6.8
PS>$a.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Single                                   System.ValueType


PS>[math]::pi
3.14159265358979
PS>([math]::pi).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Double                                   System.ValueType

Powershell uses double in calculations with fractions, by default, as was done in the first example.

Even if you are forced to use single, as in the second example, the result is presented correctly. But this may be because he made the rounding in such a way that the error is not seen.

The third example gives a pi number, which can not be represented exactly in powershell. It is an approximation although a very close approximation. If you were to make millions of calculations based on this approach, the results could be garbage.

Powershell is an interface between the user and .NET. I have not investigated which of these calculations are a matter of Powershell and which of .NET.

    
answered by 04.12.2015 в 15:23
15

As they explained in some comments this is due to how computers store and obtain floating point numbers, which brings many problems especially in applications that require high precision so there is a type of data that offers the possibility of working with more accurate calculations, it can be called different in other languages I leave you the specification in Ruby and Java.

link link

    
answered by 02.12.2015 в 17:35
14

In Javascript, you can use the toFixed () method. Which transforms the number into a string and then trims according to the number of decimals you've passed.

var num = 0.1 + 0.2;
// 0.30000000000000004
console.log(num.toFixed(2));
// 0.30
    
answered by 02.12.2015 в 09:14
10

In computing, the calculations are usually not exact: only accurate. What bothers you about the results is not their lack of precision - which is a lot - but they are not supplied in the format you want. You look for a function that transforms the numeric data into a string that has the format you like. In Visual Basic, that function is called "Format". Sometimes 3.00 is preferable before 2.99999999. In that case, you must resort to some rounding function before applying the format.

    
answered by 13.08.2017 в 22:26
6

That's because Javascript is based on the "IEEE 754" standard also called floating point.

In summary, 0.1 + 0.2 in standard "IEEE 754" the result is not exact, it is close to 0.30000000000000004, but if it is not exact, it does not help us if we want to make a comparison (for example 0.1 + 0.2 = 0.3 )

A common practice is to use a small margin of error, that value used as standard in the industry is called "epsilon machine" its value in javascript is 2 ^ -52 (2.220446049250313e-16)

from ES6 Number.EPSILON provides us with this value to be used in this type of cases

If you do not have ES6 in your environment, you can make a polyfill very easily

if (!Number.EPSILON) {
    Number.EPSILON = Math.pow(2,-52);
}

I give you an example of how to compare 2 numbers using Number.EPSILON

function numbersCloseEnoughToEqual(n1,n2) {
    return Math.abs( n1 - n2 ) < Number.EPSILON;
}

var a = 0.1 + 0.2;
var b = 0.3;

numbersCloseEnoughToEqual( a, b );                  // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 );  // false

I recommend this chapter of You dont know javascript where they explain in a very simple way why the operations are not exact in languages that use the IEEE 754 standard.

(here you will see the same example applied but more detailed and with more information about the numerical values) link

    
answered by 06.04.2018 в 04:35
3

According to what you comment that code is written in JavaScript. Based on this, the correct way to perform arithmetic operations is using the parse and toFixed methods.

If you want to add two numbers with decimals you should do it this way.

var sumar = function(){
  var $lado = 0.3,
      $altura = 3.1,
      $total = 0.0;
    
    //Operas las sumas para sacar el total
    $total = parseFloat($lado) + parseFloat($altura);
    
    console.log($total);
}

sumar();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

On the contrary, if you want to add two integers you must do it with the parseInt () method. I leave an example.

var sumar = function(){
  var $lado = 3,
      $altura = 4,
      $total = 0;
    
    //Operas las sumas para sacar el total
    $total = parseInt($lado) + parseInt($altura);
    
    console.log($total);
}

sumar();

Note that you can use parseInt or parseFloat, depending on what type of data you are using.

Now, taking as an example the second scenario that you raise, for this problem there exists the toFixed () method. This takes the decimals that you indicate within the parentheses, that is, if you put two, it will take you 0.30, according to the scenario mentioned above.

Here is an example of this.

var sumar = function(){
  var $lado = 0.1,
      $altura = 0.2,
      $total = 0.0;
    
    //Toma un decimal
    $total = (parseFloat($lado) + parseFloat($altura)).toFixed(1);
    console.log($total);
        
    //Toma dos decimales
    $total = (parseFloat($lado) + parseFloat($altura)).toFixed(2);
    console.log($total);
        
    //Toma tres decimales
    $total = (parseFloat($lado) + parseFloat($altura)).toFixed(3);
    console.log($total);
}

sumar();

I hope it serves you. Good luck in your next developments.

    
answered by 12.09.2018 в 18:43