Search code examples
iosobjective-cfloating-pointcomparisonepsilon

Objective C: Safe float comparison fails strangely


I wrote a piece of code and came across a really strange problem. A comparison between two floats returns NO even when the actual comparison is true. I even used safe floating point comparison by comparing to FLT_EPSILON. This is the code:

//To start the process run this:
[self increment:0.0f];




- (void)increment:(float)f {
    f += 0.02f;

    if ((fabs(f - 1.0f) < FLT_EPSILON)) {
        NSLog(@"STOP");
    }
    else {
        NSLog(@"F %f", f);
        [self increment:f];
    }
}

And the comparison will always fail and the code it will go into an infinite loop. I have tested this on a 32 bit device on iOS 7 and on the iPhone 5S simulator on iOS 8.


Solution

  • The problem is that you are accumulating values which are not precise. FLT_EPSILON should be defined as the smallest value such that 1.0f + FLT_EPSILON != 1.0f.

    What happens is that on each step you are adding a finite precision value to another finite precision value and you accumulate a small error. Since you are checking exactly for a value which is enough near to 1.0f to be undistinguishable from 1.0f then the check always fails.

    If you need to stop at 1.0 you should instead check for if (f > 1.0f) directly, or use a looser constraint. Mind that using f > 1.0f could yield an additional iteration if the values comes a little bit before the desired one so it could be not suitable if the amount of iterations must be precise. Something like f > 1.0 - 0.02f/2 should be more precise.

    0.98            1.0-0.02/2              1.0
     |                 |      ACCEPTABLE     |   ACCEPTABLE...   
    

    lldb on Xcode 5.1

    (lldb) p f
    (float) $0 = 0.999999582
    (lldb) p -(f - 1.0f)
    (float) $1 = 0.000000417232513
    (lldb) p __FLT_EPSILON__
    (float) $2 = 0.00000011920929
    (lldb) p (-(f - 1.0)) < __FLT_EPSILON__
    (bool) $3 = false