13

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!

Barney
  • 2,355
  • 3
  • 22
  • 37
  • 8
    "How to avoid floating-point arithmetic error" - you can't, sorry. –  Feb 13 '13 at 17:57
  • this has been asked and answered many times – mechanical_meat Feb 13 '13 at 18:01
  • 3
    `0.2` cannot be exactly represented by a `float` (assuming IEEE754 floating point arithmetic). You can see this if you increase the precision of the output: [example](http://liveworkspace.org/code/3ZXIxx$0). – Mankarse Feb 13 '13 at 18:03

6 Answers6

39

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
Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • 3
    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. – Celeritas Feb 13 '13 at 18:34
  • It reminds me the famous problems related to [ULPs] (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition) – Fabien R Aug 30 '14 at 16:05
10

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.

Srikant Krishna
  • 881
  • 6
  • 11
  • 3
    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. – Bruce Dawson Oct 18 '15 at 06:55
10

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.

BlackJack
  • 4,476
  • 1
  • 20
  • 25
bstamour
  • 7,746
  • 1
  • 26
  • 39
  • 2
    +1 for the link to this paper. For those who want HTML over PDF: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – ajp15243 Feb 13 '13 at 17:59
7

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
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
6

Use integers and divide down:

for(int value = -20; value <= 20; value += 2)
    std::cout << (value/10.0) << std::endl;
QuentinUK
  • 2,997
  • 21
  • 20
  • +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 Feb 13 '13 at 20:30
  • 1
    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). – Pascal Cuoq Feb 13 '13 at 20:42
1

Learn about floating point representation with some Algorithms book or using internet. There are lots of resources out there.

For the time, what you want seems to be some way to get zero when its something very very close to zero. and we all know that we call this process "rounding". :) so why don't you use it while printing those numbers. printf function provides good formatting power for these kinds of things. check the tables in the following link if you dont know how to format with printf. ( you can use the formating for rounding and displaying the numbers correctly ) printf ref : http://www.cplusplus.com/reference/cstdio/printf/?kw=printf

-- edit --

maybe some of you know know that according to mathematics 1.99999999.... is the same as 2.0 . Only difference is the representation. But the number is the same.

your floating point problem is a little bit similar to this. ( this is just for your clarification only. your problem is not the same as the 1.9999.... thing. )

Deamonpog
  • 805
  • 1
  • 10
  • 24