Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loss of precision converting 'float' to NSNumber, back to 'float'

I seem to be encountering a strange issue in Objective-C converting a float to an NSNumber (wrapping it for convenience) and then converting it back to a float.

In a nutshell, a class of mine has a property red, which is a float from 0.0 to 1.0:

@property (nonatomic, assign) float red;

This object is comparing itself to a value that is loaded from disk, for synchronization purposes. (The file can change outside the application, so it checks periodically for file changes, loads the alternate version into memory, and does a comparison, merging differences.)

Here's an interesting snippet where the two values are compared:

if (localObject.red != remoteObject.red) {
    NSLog(@"Local red: %f Remote red: %f", localObject.red, remoteObject.red);
}

Here's what I see in the logs:

2011-10-28 21:07:02.356 MyApp[12826:aa63] Local red: 0.205837 Remote red: 0.205837

Weird. Right? How is this piece of code being executed?

The actual value as stored in the file:

...red="0.205837"...

Is converted to a float using:

currentObject.red = [[attributeDict valueForKey:@"red"] floatValue];

At another point in the code I was able to snag a screenshot from GDB. It was printed to NSLog as: (This is also the precision with which it appears in the file on disk.)

2011-10-28 21:21:19.894 MyApp[13214:1c03] Local red: 0.707199 Remote red: 0.707199

But appears in the debugger as:

GDB Screenshot

How is this level of precision being obtained at the property level, but not stored in the file, or printed properly in NSLog? And why does it seem to be varying?

like image 751
Craig Otis Avatar asked Oct 29 '11 01:10

Craig Otis


3 Answers

If you are converting it to/from a string at any point try using %0.16f instead of %f (or whatever precision you want instead of .16).

For more info, see IEEE Std formatting.


Also, use objectForKey instead of valueForKey (valueForKey is not intended to be used on dictionaries):

currentObject.red = [[attributeDict objectForKey:@"red"] floatValue];

See this SO answer for a better explanation of objectForKey vs valueForKey:

Difference between objectForKey and valueForKey?

like image 119
chown Avatar answered Nov 13 '22 05:11

chown


The problem your are experiencing is a problem with floating point. A floating point number doesn't exactly represent the number stored (except for some specific cases which don't matter here). The example in the link craig posted is an excellent example of this.

In your code, when you write out the value to your file you write an approximation of what is stored in the floating point number. When you load it back, another approximation of it is stored in the float. However these two numbers are unlikely to be equal.

The best solution is to use a fuzzy comparison of the two floating point numbers. I'm not an objective c programmer, so I don't know if the languages includes builtin functions to preform this comparison. However this link provides a good set of examples on various ways to preform this comparison.

You can also try the other posted solution of using a bigger precision to write out to your file, but you will probably end up wasting space for the extra precision that you don't need. I'd personally recommend you use the fuzzy comparison as it is more bullet proof.

like image 27
MJD Avatar answered Nov 13 '22 04:11

MJD


You say that the "remote" value is "loaded from disk". I'm guessing that the representation on disk is not an IEEE float bit value, but rather some sort of character representation or some such. So there are inevitable conversion errors going to and from that representation, given the way IEEE float works. You will not get an exact result, given that there's only about 6 digits of decimal precision in a float value, but it rarely maps to exactly 6 decimal digits but instead is sort of like representing 1/3 in decimal -- there is no exact mapping.

like image 36
Hot Licks Avatar answered Nov 13 '22 04:11

Hot Licks