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?
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."
Related
This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 6 years ago.
For some reason, certain Doubles in my Swift app are giving me trouble when converting to NSNumber, while some are not. My app needs to convert doubles with 2 decimal places (prices) to NSNumbers so they can be stored and retrieved using Core Data. For example, a few particular prices such as 79.99 would evaluate to 99.98999999999999 unless specifically formatted using NSNumber's doubleValue method.
Here selectedWarranty.price = 79.99 as shown in debugger
// item.price: NSNumber?
// selectedWarranty.price: Double?
item.price = NSNumber(double: selectedWarranty.price!)
I programmed some print statements to show how the conversion works out
Original double: 79.99
Converted to NSNumber: 79.98999999999999
.doubleValue Representation: 79.99
Can somebody explain if there is a reason why the initializer cannot surely keep 2 decimal places for every number? I would really like to store the prices in Core Data like they should be. Formatting every time it is displayed doesn't sound very convenient.
UPDATE:
Converted Core Data object to type NSDecimalNumber through data model, 79.99 and 99.99 no longer a problem, but now more manageable issue with different numbers...
Original double: 39.99
Converted to NSDecimalNumber: 39.99000000000001024
Firstly, you're confusing some terms. 79.98999999999999 is higher precision than 79.99 (it has a longer decimal expansion), but lower accuracy (it deviates from the true value).
Secondly, NSNumber does not store neither 79.99 nor 79.98999999999999. It stores the magnitude of the value according to the IEEE 754 standard. What you're seeing is likely the consequence of the printing logic that's applied to convert that magnitude into a human readable number. In any case, you should not be relying on Float or Double to store values with a fixed precision. By their very nature, they sacrifice precision in order to gain a longer range of representable values.
You would be much better off representing prices as an Int of cents, or as an NSDecimalNumber.
Please refer to Why not use Double or Float to represent currency?
That's how double works everywhere. If you need only 2 decimal places consider using integer/long instead adding point after second digit, when need to display the value.
For simple uses, such as tracking weight values like 65.1kg, is there any benefit of going with NSDecimal/NSDecimalNumber over double?
My understanding here is double (or even float) provides more than enough precision in such cases. Please correct me if I'm wrong.
First, read Josh Caswell's link. It it especially critical when working with money. In your case it may matter or may not, depending on your goal. If you put in 65.1 and you want to get exactly 65.1 back out, then you definitely need to use a format that rounds properly to decimal values like NSDecimalNumber. If, when you put in 65.1, you want "a value that is within a small error of 65.1," then float or double are fine (depending on how much error you are willing to accept).
65.1 is a great example, because it demonstrates the problem. Here in Swift because its so easy to demonstrate, but ObjC is the same:
1> 65.1
$R0: Double = 65.099999999999994
2>
1/10 happens to be a repeating decimal in binary, just like 1/3 is a repeating decimal in decimal. So 65.1 encoded as a double is "close to" 65.1, but not exact. If you need an exact representation of decimal-encoded number (i.e. what most humans expect), use NSDecimalNumber. This isn't to say that NSDecimalNumber is more accurate than double. It just imposes different rounding errors than double. Which rounding errors you prefer depends on your use case.
As we know, there is float number precision problem when comparing float numbers.
Also that NSSet uses equality compare to keep elements unique in it.
So what happens when NSSet stocks NSValues that have float/double in it,
Should we never use float in NSSet to avoid float precision problem?
there is float number precision problem when comparing float numbers.
To be precise, only comparison for equality presents a problem. Unfortunately, that's the kind of comparison done by NSSet.
So what happens when NSSet stocks NSValues that have float/double in it?
It treats numbers that are really close to each other but not exactly equal as different items in the set. That is why one should be extremely careful when using floating point numbers in NSSet. Ideally, you should avoid it if you can.
I have a function that returns a float value like this:
1.31584870815277
I need a function that returns TRUE comparing the value and the two numbers after the dot.
Example:
if 1.31584870815277 = 1.31 then ShowMessage('same');
Sorry for my english.
Can someone help me? Thanks
Your problem specification is a little vague. For instance, you state that you want to compare the values after the decimal point. In which case that would imply that you wish 1.31 to be considered equal to 2.31.
On top of this, you will need to specify how many decimal places to consider. A number like 1.31 is not representable exactly in binary floating point. Depending on the type you use, the closest representable value could be less than or greater than 1.31.
My guess is that what you wish to do is to use round to nearest, to a specific number of decimal places. You can use the SameValue function from the Math unit for this purpose. In your case you would write:
SameValue(x, y, 0.01)
to test for equality up to a tolerance of 0.01.
This may not be precisely what you are looking for, but then it's clear from your question that you don't yet know exactly what you are looking for. If your needs are specifically related to decimal representation of the values then consider using a decimal type rather than a binary type. In Delphi that would be Currency.
If speed isn't the highest priority, you can use string conversion:
if Copy(1.31584870815277.ToString, 1, 4) = '1.31' then ShowMessage('same');
I want to perform some simple arithmetic on NSNumbers and preserve the type. Is this possible?
For example:
- (NSNumber *)add:(NSNumber *)firstNumber to:(NSNumber *)secondNumber;
Is my method definition, and firstNumber and secondNumber are integers then I would like to return an integer that is the integer addition of them both. Equally if both are doubles then to return the result as a double.
It looks like I can get the type (except for boolean) using [NSNumber objCType] as found in this question: get type of NSNumber but I can't seem to extract those types and do the calculation without lots of code to extract the values do the calculation and return the result for every possible type.
Is there a short and concise way of doing this?
If you want to perform arithmetic the best bet would be using an NSDecimalNumber.
NSDecimalNumber have methods to perform arithmetic operations like :
– decimalNumberByAdding:
– decimalNumberBySubtracting:
– decimalNumberByMultiplyingBy:
– decimalNumberByDividingBy:
– decimalNumberByRaisingToPower:
– decimalNumberByMultiplyingByPowerOf10:
– decimalNumberByAdding:withBehavior:
– decimalNumberBySubtracting:withBehavior:
– decimalNumberByMultiplyingBy:withBehavior:
– decimalNumberByDividingBy:withBehavior:
– decimalNumberByRaisingToPower:withBehavior:
– decimalNumberByMultiplyingByPowerOf10:withBehavior:
And since NSDecimalNumber extends NSNumber it also have all methods of an NSNumber, so i think that you could use it in your case without any problem.
For nearly all applications it will be fine to convert to double and back using -doubleValue and –initWithDouble:. This will let you use the standard C symbols (+, -, ...) and functions (exp(), sin()). The only way you would run into trouble is if you were using close to the full precision for 64-bit integer values.
If you want to stick with Objective-C class operations you can use NSDecimalNumber instead.
See also: How to add two NSNumber objects?
How about calculating the expression value as a double (with all the inputs as double values), and then checking if the result is an integer? Then you just use NSNumber numberWithInt: or NSNumber numberWithDouble: to return the result.
When you check if the result value is an integer, be sure to account for the rounding error (e.g. when 1 is expressed as 0.99999999, etc).
EDIT: Just noticed in the docs for NSNumber this phrase:
Note that number objects do not necessarily preserve the type they are
created with.
I think this means you can't reliably do what you're trying to do.