Question

I have some method that receives a float as an argument, and checks to see if this float is in an array. To do this, I first convert the float to a NSNumber. This is a testable simplification of my code:

float aFloat = 0.3;
NSNumber *aNSNumber = @(aFloat);

NSArray *anArray = @[@(0.0), @(0.3), @(1.0)];
NSLog(@"%d", [anArray containsObject:aNSNumber]);

This code will log 0 (i.e. NO), so it's saying that 0.3is not in anArray. If aFloat is a "round" number such as 0.0, 0.5, or 1.0, the test works and logs 1 (i.e. YES). Any number other than that, like the 0.3 above, fails.

In the other hand, if we change aFloat to be a double, it works. Or, if we change anArray to this:

NSArray *array = @[[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.3], [NSNumber numberWithFloat:1.0]];

It also works. What I presumed is that the NSNumber @() notation creates a numberWithDouble:.

But, my question is, shouldn't it work even when aFloat is a float? Since I'm "converting" it anyways by saving it in aNSNumber... And shouldn't it automatically recognize that the float 0.3 and the double 0.3 are actually the same numbers? Also, why the "round" numbers works anyway?

Was it helpful?

Solution

@(0.3) uses -numberWithDouble: because 0.3 has type double. If you wrote @(0.3f) then it would use -numberWithFloat:.

Neither float nor double can store 0.3 exactly. (It's similar to the problem of writing 1/3 in decimal form - you can't do it exactly using a finite number of digits.) Instead, you get the float closest to 0.3 and the double closest to 0.3. These two numbers are not equal to each other, so -containsObject: can't find a match.

Both float and double can store 0.0 and 1.0 exactly, so both conversions give you the same result and -containsObject: succeeds.

OTHER TIPS

The @(0.3) in the anArray is a double wrapped in an NSNumber. And of course your aFloat is a float wrapped in an NSNumber.

Try one of the two possible changes:

1) Change float aFloat to double aFloat

or

2) Change @(0.3) in anArray to @(0.3f).

You can visualize the difference between 0.3 and 0.3f with this snippet:

NSLog(@"%.20f %.20f", 0.3, 0.3f);

My debugger shows: 0.29999999999999998890 0.30000001192092895508

Funny enough, the '0.3' appears more accurate than the '0.3f'. Anyway, I speculate that the compiler may take the straight 0.3 as a double first before converting it to a float whereas the 0.3f specification is a float immediately.

Another thing to observe is had you done this:

NSArray *anArray = @[@(0.0), aNSNumber, @(1.0)];

The containsObject call would have succeeded. Perhaps the compiler's route to convert the float into whatever it uses internally between the aFloat declaration and the literal has some differences. Just speculating here...

In general, I don't like testing for equality with floats or doubles for that matter. Accuracy problems can through you for a loop just too easily.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top