6

I have a calculation that goes something like this:

Price = value * randomNumberBetween(decimalValueA, decimalValueB)

I was originally generating this using floats/doubles. However, after looking up a bit more on objective-c, it was mentioned numerous times that when calculating currency you should use NSDecimalNumber. The issue I have is that I use this 'price' variable in comparisons and things, for example:

if (deposit/price) < 0.2
    return price*0.05;

Using NSDecimalNumber makes this a lot more difficult. As far as I'm aware I should be converting any magic numbers (in this case 0.2 and 0.05) to NSDecimalNumber so then I can compare them and use functions such as NSDecimalMultiply.

Also, if I have a function that is something like:

return (minRandomPercentage + ((maxRandomPercentage - minRandomPercentage) * (randomNumber)

it ends up becoming this ridiculous string of nested function calls like:

return [minRandomPercentage decimalNumberByAdding:[[maxRandomPercentage decimalNumberBySubtracting:  minRandomPercentage] decimalNumberByMultiplyingBy:random]]

Is this seriously how objective-c deals with decimals? Can anyone give me any clues on how to make this a lot less arduous? I can live with the nested function calls if I could do comparisons with the result and not have to be casting every magic number I have.

dnatoli
  • 6,972
  • 9
  • 57
  • 96
  • If you're dealing with currency, why aren't you working in terms of primitive integer datatypes? If your values are expressed in "cents" rather than "dollars" (substitute your own i18n), you needn't worry about fractional amounts. – Aidan Steele Apr 01 '11 at 03:27

2 Answers2

10

If you can't afford to deal with the rounding errors that can occur with the standard base-2 floating point types, you'll have to use NSDecimal or NSDecimalNumber. NSDecimal is a C struct, and Foundation provides a C interface for dealing with it. It provides functions NSDecimalAdd, NSDecimalMultiply, etc.

From the Number and Value Programming Guide: You might consider the C interface if you don’t need to treat decimal numbers as objects—that is, if you don’t need to store them in an object-oriented collection like an instance of NSArray or NSDictionary. You might also consider the C interface if you need maximum efficiency. The C interface is faster and uses less memory than the NSDecimalNumber class.

If you're writing object-oriented code, and you're not interacting with massive data sets, it might be best to stick with NSDecimalNumber. If you profile your code and find that using NSDecimalNumber is causing a high memory overhead, then you may need to consider alternatives.

If rounding errors are not a concern, you can also use native C scalars. See: How to add two NSNumber objects?

NSNumber and NSDecimalNumber are used as object wrappers when you need to pass a number to a method or store numbers in a collection. Since NSArray, NSSet, NSDictionary, etc. only allow you to store objects of type 'id', you can't store ints, floats, etc. natively.

If you're dealing with large data sets and can afford rounding errors, you can use ints, floats, doubles, etc. raw. Then when you have your result and you need to store it or pass it to another object, you can wrap it up in an NSNumber accordingly.

If you do have a need to store large collections of numbers, it's much more efficient to use C arrays than to initialize and store lots of NSNumber objects.

Community
  • 1
  • 1
Stephen Poletto
  • 3,645
  • 24
  • 24
  • But if I use the scalar c types then I don't get the accuracy of decimals do I? I believe there is a decimal type in C, but the header for that doesn't exist in the objective-c library... – dnatoli Apr 01 '11 at 03:02
  • @link664 - I've clarified my answer. – Stephen Poletto Apr 01 '11 at 03:26
1

Seriously, this is how you do base 10 arithmetic in iOS. As you're probably aware, many numbers that have exact representations in base 10 don't have exact representations in base 2, and that can lead to unacceptable rounding when working with base 10 systems like currency or metric measurements.

Values represented by NSDecimalNumber are objects, unlike built-in numeric types like int, float, and double. It seems odd at first to use methods for arithmetic operations, but it makes more sense when you start thinking about the values as objects.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • So do I have to convert all magic numbers in order to use them? For example I would like to be able to say [NSDecimalNumber numberWithFloat:0.25] but it gives me a warning saying "Incompatible pointer types sending 'NSNumber*' to parameter type 'NSDecimalNumber*' since numberWithFloat is an NSNumber function. – dnatoli Apr 01 '11 at 03:20
  • 1
    @link664: http://stackoverflow.com/questions/453691/how-use-nsdecimalnumber/454562#454562 – Stephen Poletto Apr 01 '11 at 03:35