Converting Double to NSNumber in Swift Loses Accuracy [duplicate]
Asked Answered
W

2

12

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
Welltodo answered 24/8, 2016 at 15:42 Comment(4)
Can you please make a minimal, complete and verifiable example?Routine
Use NSDecimalNumber instead of NSNumber for currency.Captivity
I can't reproduce this issue. swiftlang.ng.bluemix.net/#/repl/57bdc2590dcf4abf47da28c2Routine
1> 79.99 $R0: Double = 79.989999999999994. But I tried other numbers, quite interesting. 2> 39.99 $R1: Double = 39.990000000000002 3> 59.99 $R2: Double = 59.990000000000002 4>Welltodo
R
12

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?

Routine answered 24/8, 2016 at 15:52 Comment(2)
Thanks for the explanation. NSDecimalNumber route seems best. My only question is how I can get the Core Data model to let me use that instead of NSNumber? I am not very experienced in this areaWelltodo
I don't know, try opening a new question for it. If you feel this answer satisfies your immediate question, please mark it as accepted :)Routine
H
1

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.

Hesky answered 24/8, 2016 at 16:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.