0

Yes, I've read the other posts on stackoverflow about comparing NSNumber and none of them seem to quite address this particular situation.
This solution was particularly bad ... NSNumber compare: returning different results
Because the suggested solution doesn't work at all. Using abs(value1 - value2) < tolerance is flawed from the start because fractional values are stripped off, making the tolerance irrelevant.

And from Apple documentation... NSNumber explicitly doesn't guarantee that the returned type will match the method used to create it. In other words, if you're given an NSNumber, you have no way of determining whether it contains a float, double, int, bool, or whatever.

Also, as best I can tell, NSNumber isEqualToNumber is an untrustworthy method to compare two NSNumbers.

So given these definitions...

NSNumber *float1 = [NSNumber numberWithFloat:1.00001];
NSNumber *double1 = [NSNumber numberWithDouble:1.00001];

If you run the debugger and then do 2 comparisons of these identical numbers using ==, one fails, and the other does not.

p [double1 floatValue] == [float1 floatValue]   **// returns true**
p [double1 doubleValue] == [float1 doubleValue]   **// returns false**

If you compare them using isEqualToNumber

p [float1 isEqualToNumber:double1]             **// returns false**

So if isEqualToNumber is going to return false, given that the creation of an NSNumber is a black box that may give you some other type on the way out, I'm not sure what good that method is.

So if you're going to make a test for dirty, because an existing value has been changed to a new value... what's the simplest way to do that that will handle all NSNumber comparisons.. not just float and double, but all NSNumbers?

It seems that converting to a string value, then compariing would be most useful, or perhaps a whole lot of extra code using NSNumberFormatter.

What are your thoughts?

Community
  • 1
  • 1
Logicsaurus Rex
  • 3,172
  • 2
  • 18
  • 27
  • Your `float3` doesn't include a definition. Did you mean `float1`? – Rob Napier May 14 '15 at 00:50
  • 2
    @user3055655 You probably just need to use `fabs` or `fabsf` for a tolerance check rather than `abs` – Jesse Rusak May 14 '15 at 01:45
  • @user3055655 Excellent suggestion. I'll see if that will work out as well. You missed one instance of abs(...) in the answer you fixed. It's several lines above the part you fixed. Thanks again. – Logicsaurus Rex May 14 '15 at 01:53

1 Answers1

4

It is not possible to reliably compare two IEEE floats or doubles. This has nothing to do with NSNumber. This is the nature of floating point. This is discussed in the context of simple C types at Strange problem comparing floats in objective-C. The only correct way to compare floating point numbers is by testing against a tolerance. I don't know what you mean by "fractional values are stripped off." Some digits are always lost in a floating point representation.

The particular test value you've provided demonstrates the problems quite nicely. 1.00001 cannot be expressed precisely in a finite number of binary digits. Wolfram Alpha is a nice way to explore this, but as a double, 1.00001 rounds to 1.0000100000000001. As a float, it rounds to 1.00001001. These numbers, obviously, are not equal. If you roundtrip them in different ways, it should not surprise you that isEqualToNumber: fails. This should make clear why your two floatValue calls do turn out to be equal. Rounded to the precision of float, they're "close enough."

If you want to compare floating point numbers, you must compare against an epsilon. Given recent advances in compiler optimization, even two identical pieces of floating point C code can generate slightly different values in their least-significant digits if you use -Ofast (we get big performance benefits by allowing that).

If you need some specific number of significant fractional digits, then it is usually better to work in fixed point notation. Just scale everything by the number of digits you need and work in integers. If you need floating point, but just want base-10 to work well (rather than base-2), then use NSDecimalNumber or NSDecimal. That will move your problems to things that round badly in base-10. But if you're working in floating point, you must deal with rounding errors.

For a much more extensive discussion, see "What Every Programmer Should Know About Floating-Point Arithmetic."

Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I like "scale everything and work with integers" That intuitively seems like it would work, so I'll go down that road. What I meant about fraction values being stripped off is the abs function (absolute) value is going to strip off the .246 from 18.246, leaving you to comparing integers rather than a specific number of significant digits. Now if you throw in the extra step of multiplying by 100 for example, THEN comparing... yeah, I think that's going to work. – Logicsaurus Rex May 14 '15 at 01:21