tags:

views:

82

answers:

4

I was writing a simple for loop recently and got some unexpected behavior:

for(double x = 0.0; x <= 1.0; x += 0.05)
{
    Console.WriteLine(x.ToString());
}

This is the output:

0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
0.45
0.5
0.55
0.6
0.65
0.7
0.75
0.8
0.85
0.9
0.95

Notice that 1 doesn't appear even though the condition for continuing the for loop seems to include it. I realize that the reason for this is because decimal numbers are being stored in memory as binary, i.e. 1 is not really exactly 1 but actually 1.0000000000000002 (according to the variable watch in Visual Studio). So my question is, what is the best way to avoid this unexpected behavior? One way would be to use the decimal type instead of double, but most of the System.Math functions work on doubles only, and casting between the two isn't straightforward.

+9  A: 

Don't test doubles for equality.

Here you could use integer arithmetic instead:

for (int i = 0; i <= 20; ++i)
{
    double x = (double)i / 20.0;
    Console.WriteLine(x);
}

In other cases it might be more appropriate to test if the difference between two doubles is sufficiently small rather than using an equality comparison.

Mark Byers
+2  A: 

always manage yourself the rounding, don't let the system handle it

  for(double x = 0.0; Math.Round(x,2,MidpointRounding.AwayFromZero) <= 1.0; x += 0.05)
  {
      Console.WriteLine(x.ToString());
  }

will output

0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
0.45
0.5
0.55
0.6
0.65
0.7
0.75
0.8
0.85
0.9
0.95
1

Fredou
This answer seems best to me, although none are ideal. I don't like the idea of iterating over something different just to avoid floating-point errors. I wish there was a way to tell the compiler to "round sanely", e.g. it is probably safe to assume 1.0000000000000002 is really 1 but in lieu of that I think a manual Math.Round() is the way to go.
Craig
in lieu of what exactly? You have to consider significant digits when doing any math with decimals.
Brian Leahy
A: 

can you not scale up and use int

 for(int ix=0; ix < 1000; ix += 50)
 {

  }

then divide by 1000 if you need to. but this way you get precise loop behaviour

pm100
+1  A: 

This is referred to as floating-point error and a lot has been written about it. One solution for your case would be to define an error, such as

err = .000001

and do the comparison to within err of 1.0.

Rob Lourens