views:

339

answers:

2

I've got a plist (created in XCode) with an array full of "Numbers" (0.01, 1, 2, 6) that unpacks into NSValues when reconstituted with initWithContentsOfFile. How can I turn these NSValues into NSDecimalNumbers that I can use for adding together? They will be treated as currency values so only need precision of 2 (maybe 4) decimal places.

I've tried saving the plist values as "String" instead of "Number" and using NSDecimalNumber's initWithString to set the value but then NSValue doesn't respond to stringValue.

Seems like dealing with numbers is particularly confusing in Cocoa. so many container formats in so many frameworks... :-(

A: 

The first lesson to learn is that when representing currency, use integers instead of floating-point (decimal) numbers if you want any kind of accuracy. (Divide by 100.0 whenever you need to display cents, etc.) Computers are flawless with binary (base 2) but if you try to represent in binary something that can't be broken down into factors of 1/(2^n), you'll run into precision errors. (Try 0.1 + 0.1 and see what you get.)

That said, the XML tag within which you specify the number definitely makes a difference in how the values are interpreted in terms of Cocoa classes when you use something like -[NSArray initWithContentsOfFile:] to "reconstitute" it. Consult the plist man page and this Apple article for more details and examples.

To accomplish what you're asking, make sure you're using <real> or <integer> (and the matching closing tag) around the values in your plist. (Property List Editor and Xcode should automatically use the correct one based on whether the number has a decimal point.) In my tests, both real and integer numbers were read in as NSNumber objects. NSDecimalNumber is a subclass of NSNumber, but I'm not entirely sure how the toll-free bridging with CFNumber works in all cases. Experimentation is probably the best way to figure that out.

Quinn Taylor
The multiply by 100 approach is great for MOST currencies but it's not universal. I suppose I could get the maximumFractionDigits from NSNumberFormatter for the current locale and then multiply my integer by 10^maxDigits. Still, in this modern age of computing, it doesn't seem like it should be this difficult/confusing to manipulate something as common as currency...
Meltemi
You have a point, it would be nice if it weren't this confusing. However, as a binary/decimal issue, I don't see an easy way around it unless we switch our money to binary. :-)
Quinn Taylor
A: 

You should be able to directly store your numbers as strings in the property list. You don't need to do any NSValue wrapping for NSStrings when storing them in a plist. I'd recommend keeping the numbers in your application as NSDecimals or NSDecimalNumbers to avoid any floating-point errors, reading them from the plist using initWithString:locale:, and writing them to the plist using descriptionWithLocale:. Storing and retrieving the decimals as strings avoids any to-and-from floating point conversion errors.

Brad Larson