Title: Control a loop that uses floating-point values in C#
Sometimes you might need to use a floating-point variable that loop over a range of values. For example, you might want a value to step through the values 0.0 through 1.0 in increments of 0.1. The sequence of numbers would be 0.0, 0.1, 0.2, ..., 1.0.
Perhaps the most obvious approach would be to use a for loop as in the following code.
for (float f = 0; f <= 1f; f += 0.1f)
{
lstFloatLoop.Items.Add(f);
}
Unfortunately, floating-point values do not always store values exactly. In a loop such as this one, rounding errors add up over time so the error can grow as the loop executes.
If you look at the first column in the picture, you can see that rounding errors add up over time. The 10th entry is 0.9000001 instead of 0.9. When you add 0.1 to that result, the result is greater than 1.0 so the loop does not include it. The loop only executes 10 times instead of the 11 times that you want.
The following code shows a better approach.
float value = 0f;
for (int i = 0; i < 11; i++)
{
lstIntLoop.Items.Add(value);
value += 0.1f;
}
This example uses a float variable declared outside the loop to keep track of the values being generated and then uses an integer to control the loop. The computer can store integers exactly and they are not subject to accumulating rounding errors, so this gives you exactly the number of iterations you want. If you look at the second column in the picture, you'll see that this version gives the correct number of values, although it still shows increasing rounding errors.
The values in the picture's third column were produced by the following code.
double dvalue = 0.0;
for (int i = 0; i < 11; i++)
{
lstDoubleLoop.Items.Add(dvalue);
dvalue += 0.1;
}
This version is similar to the preceding one except it stores its values in a double instead of in a float. The accumulating rounding errors are smaller but still present.
The values in the picture's final column were produced by the following code.
for (int i = 0; i < 11; i++)
{
lstIntLoop2.Items.Add(i * 0.1);
}
This code doesn't use a variable to track the loop's values. Instead, it uses multiplication to calculate each value separately. Because it uses only one calculation for each value, rounding errors cannot accumulate over time. The results are still not perfect because floating-point variables cannot exactly represent every fractional decimal value, but these results are better than any of the others.
There are three lessons here.
- Don't use a floating point value to control a loop. Use an integer instead.
- To minimize rounding errors, use a double not a float.
- Don't accumulate values because that also accumulates rounding errors. Instead, calculate each value directly if possible.
Of course for many applications a float and some rounding error is good enough. For example, a drawing application doesn't really need to calculate each point's location to within a millionth of a pixel.
Download the example to experiment with it and to see additional details.
|