What if you wrote the following code:
int x = 0;
for(int i = 0; i< 100; i++)
{
x = x + 3.5;
NSLog(@"%d",x);
}
you wouldn't be surprised that it doesn't print out 3.5
, 7.0
, 10.5
, right? Would you say that "int is inaccurate"? Of course not.
Exactly the same thing is happening in your example. Just like 3.5
isn't representable as an integer, 0.01
isn't representable as a double. The actual value you get is:
0.01000000000000000020816681711721685132943093776702880859375
Now, you accumulate not only the initial representation error from rounding 0.1
to double, but you also get rounding errors because not all of the intermediate sums are representable. This second source of error does not occur in the int
example. The actual intermediate sums that are computed are:
0.01000000000000000020816681711721685132943093776702880859375
0.0200000000000000004163336342344337026588618755340576171875
0.0299999999999999988897769753748434595763683319091796875
0.040000000000000000832667268468867405317723751068115234375
0.05000000000000000277555756156289135105907917022705078125
0.060000000000000004718447854656915296800434589385986328125
...
0.990000000000000657252030578092671930789947509765625
1.0000000000000006661338147750939242541790008544921875
when you round these to six decimal places via the %f
format specifier, you get yet a third source of rounding, but the errors are all small enough that the results you "expect" are printed out.
Now let's look at what happens when you use float
instead of double
; because of the C arithmetic operand promotion rules, the additions are all carried out in double
, but the result is rounded back to float
after each addition -- yet another rounding. The sequence of intermediate values is as follows:
0.00999999977648258209228515625
0.0199999995529651641845703125
0.02999999932944774627685546875
0.039999999105930328369140625
0.0500000007450580596923828125
0.060000002384185791015625
...
0.809999525547027587890625
0.819999516010284423828125
0.829999506473541259765625
Up to this point, the errors are small enough that they all still produce the "expected" value when rounded to six decimal digits. However, the next value computed is
0.839999496936798095703125
Because this is just smaller than the exact halfway case for rounding to six decimal digits:
0.8399995
it rounds down, and the number that is printed is:
0.8399999
Now, what can you do about it? The reason that the errors eventually become large enough to appear when printed with six decimal digits is that you are accumulating error with each sequential addition. If you can avoid this accumulation, the error will remain small enough to not cause this trouble. There are a several easy ways to avoid this cumulative error; perhaps the easiest is:
for (int i=0; i<100; i++) {
float x = (i+1)/100.f;
NSLog(@"%d",x);
}
This works because both i + 1
and 100.f
are exactly represented in float
. There is thus only a single rounding, which occurs in the division, so the float
result is as close to your desired number as possible; there is no way you can come any closer.