C++ How to avoid floating-point arithmetic error

27,058

Solution 1

This is because floating point numbers have only a certain discrete precision.

The 0.2 is not really a 0.2, but is internally represented as a slightly different number.

That is why you are seeing a difference.

This is common in all floating point calculations, and you really can't avoid it.

Solution 2

As everybody else has said, this is do to the fact that the real numbers are an infinite and uncountable set, while floating point representations use a finite number of bits. Floating point numbers can only approximate real numbers and even in many simple cases are not precise, due to their definition. As you have now seen, 0.2 is not actually 0.2 but is instead a number very close to it. As you add these to value, you accumulate the error at each step.

As an alternative, try using ints for your iteration and dividing the result to get it back in the domain you require:

for (int value = -20; value <= 20; value += 2) {
  std::cout << (value / 10.f) << std::endl;
}

For me this gives:

-2
-1.8
-1.6
-1.4
-1.2
-1
-0.8
-0.6
-0.4
-0.2
0
0.2
0.4
0.6
0.8
1
1.2
1.4
1.6
1.8
2

Solution 3

There's no clear-cut solution for avoid floating point precision loss. I would suggest having a look through the following paper: What every computer scientist should know about floating point arithmetic.

Solution 4

Let's do your loop, but with increased output precision.

code:

for(float value = -2.0; value <= 2.0; value += 0.2)
    std::cout << std::setprecision(100) << value << std::endl;

output:

-2
-1.7999999523162841796875
-1.599999904632568359375
-1.3999998569488525390625
-1.19999980926513671875
-0.999999821186065673828125
-0.79999983310699462890625
-0.599999845027923583984375
-0.3999998569488525390625
-0.19999985396862030029296875
1.460313825418779742904007434844970703125e-07
0.20000015199184417724609375
0.400000154972076416015625
0.6000001430511474609375
0.800000131130218505859375
1.00000011920928955078125
1.20000016689300537109375
1.40000021457672119140625
1.60000026226043701171875
1.80000030994415283203125

Solution 5

Use integers and divide down:

for(int value = -20; value <= 20; value += 2)
    std::cout << (value/10.0) << std::endl;
Share:
27,058
Barney
Author by

Barney

Software Engineer

Updated on July 09, 2022

Comments

  • Barney
    Barney almost 2 years

    I am writing a loop that increments with a float, but I have come across a floating-point arithmetic issue illustrated in the following example:

    for(float value = -2.0; value <= 2.0; value += 0.2)
        std::cout << value << std::endl;
    

    Here is the output:

    -2
    -1.8
    -1.6
    -1.4
    -1.2
    -1
    -0.8
    -0.6
    -0.4
    -0.2
    1.46031e-07
    0.2
    0.4
    0.6
    0.8
    1
    1.2
    1.4
    1.6
    1.8
    

    Why exactly am I getting 1.46031e-07 instead of 0? I know this has something to do with floating-point errors, but I can't grasp why it is happening and what I should do to prevent this from happening (if there is a way). Can someone explain (or point me to a link) that will help me understand? Any input is appreciated. Thanks!

  • ajp15243
    ajp15243 about 11 years
    +1 for the link to this paper. For those who want HTML over PDF: docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
  • Celeritas
    Celeritas about 11 years
    I can't believe this is not the accepted answer, seeing as this and 1 other answer are the only ones that give a solution.
  • Pascal Cuoq
    Pascal Cuoq about 11 years
    +1, but… by dividing value by 10.0, you are suggesting to the compiler that it should compute with double precision, and then convert to single precision (the OP's program you are trying to emulate has a single-precision variable). It so happens that this gives the same result as a straight single-precision division. But, as the reason why it gives identical results is non-trivial, the compiler will almost certainly generate code for a double-precision division followed by conversion from double to single-precision. For this reason, value / 10.0f would be marginally better.
  • Pascal Cuoq
    Pascal Cuoq about 11 years
    I just checked and GCC does generate a single-precision division for float r = f / 10.0;. I am impressed (and my previous comment loses much of its value).
  • Fabien R
    Fabien R over 9 years
    It reminds me the famous problems related to [ULPs] (randomascii.wordpress.com/2012/02/25/…)
  • Bruce Dawson
    Bruce Dawson over 8 years
    It is important to point out that while 0.2 cannot be exactly represented as a float, -2.0 and 2.0 can. I point this out only to avoid the impression that floating-point math is arbitrary and capricious. All that is happening is that float and double use base 2, and 0.2 is equivalent to 1/5, which cannot be represented as a finite base 2 number. -2, 2.0, 0.5, 0.25, -.375 and 178432 can all be represented exactly.