Floating Point Rounding Errors in Kiwi

I ran into a problem this morning caused by unexpected floating point rounding differences. Comparing floating point numbers can be tricky. For example, a floating point number may represent the value one as 0.99999… So testing for the value of a floating point number may require specifying a precision (eg. 1.0 +/- 0.001). This is because 0.999 does not equal 0.999999.

Kiwi provides some support for this. I can test a floating point value using this type construct:

   [[theValue(myFloat) should] equal:1.0 withDelta:0.001];

However, I ran into a problem when specifying expected floating point arguments for a stub. There doesn’t appear to be a way to specify precision for expected arguments.

For example, the following code tests to ensure that the code is persisting a specified floating point value to NSUserDefaults:

// Code under test
- (void)setMyValue:(float)myValue {    
   [[NSUserDefaults standardUserDefaults]setFloat:myValue 
                                           forKey:@"my_value"];
}
// Kiwi test
   ...
   it(@"persists myValue to NSUserDefaults", ^ {
      id mockNSDefaults = [NSUserDefaults mock];
      [[NSUserDefaults stubAndReturn:mockNSDefaults] standardUserDefaults];
      [[mockNSDefaults should] receive:@selector(setFloat:forKey:)
                         withArguments:theValue(123.4),@"my_value"];
      sut.myValue = 123.4;
   });

This code looks pretty straight forward. It creates a mock object for an NSUserDefaults instance, and returns it when the class method [NSUserDefaults standardUserDefaults] is called (hurray class method stubs!). It then verifies that the value 123.4 is passed to the setFloat:forKey method. Evidently 123.4 isn’t exactly 123.4. It appears that that one of the values is being handled with higher precision (double) than the other (float). Then when compared, they are not exactly equal.

This problem can be fixed a couple ways:

  1. Specify the precision of the values (123.4f instead of 123.4).
    This should work in most cases, but might not depending on what’s going on under the covers.
  2. Use constants that convert from decimal to binary exactly.
    For example, the value 128.0 passes in the above example, but 123.4 doesn’t.

In the above example, I simply changed 123.4 to 123.4f in the first occurrence of 123.4, and the tests then passed as expected.

Although this problem occurred in Kiwi, you may run into similar situations in other frameworks when dealing with floating point numbers. Caveat Emptor.

2 thoughts on “Floating Point Rounding Errors in Kiwi

  1. Wow, class methods stubbing!! I’ve missed this feature)) Thanks for your example! It should fix the problem with `[NSBundle mainBundle]`)

    And in your Kiwi test it seems that should be used `my_persisted_value` as a key instead of `my_value`. Or maybe vice versa: `my_value` should be used as a key in your `setMyValue:` method.

  2. Thanks for the feedback. I’m taking this code from real code I’m working with, and trying to clean it up and simplify it as I post it. I’ve fixed the different key names to both be “my_value”.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.