0

Possible Duplicate:
Trouble with floats in Objective-C

It might sound to you like a basic issue, but I'm trying for 2 days to figure it out. I searched for a solution and the only explanation I found is that float uses only 4-bytes of memory. (that's helpful...) I have the following loop:

double x = 0.0;
        for(int i = 0; i< 100; i++)
        {
            x = x + 0.01;
            NSLog(@"%f",x);
        }

and it prints:

0.010000
0.020000
0.030000
.
.
1.000000

but when I change the double to float:

0.010000
0.020000
0.030000
.
.
0.820000
0.830000
0.839999
0.849999
.
.
0.999999

As you can see, the computer can't calculate 0.840000 as float -_- The problem is that I have to use float because I'm using the UIProgressView that can take a float number between 0.0 to 1.0.

If I can't use double, what can I do? Thanks.

Community
  • 1
  • 1
Noam Solovechick
  • 1,127
  • 2
  • 15
  • 29
  • 8
    [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – David Rönnqvist Jan 30 '13 at 15:55

2 Answers2

3

Use x = (i + 1) * 0.01; instead of x = x + 0.01;. It's still inaccurate as float simply cannot exactly represent 0.84, but at least the errors don't accumulate.

Henrik
  • 23,186
  • 6
  • 42
  • 92
3

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.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269