47

I'm building a app that needs to perform calculations on money.

I wonder how to properly use NSDecimalNumber, especially how to initialize it from integers, floats & doubles?

I only found it easy to use the -decimalNumberWithString: method. The -initWith... methods are discouraged so that only left the ones with mantissa, but never in any of 7 languages I used before did I need that so I don't know what put there...

Isuru
  • 30,617
  • 60
  • 187
  • 303
mamcx
  • 15,916
  • 26
  • 101
  • 189

4 Answers4

83

Do NOT use NSNumber's +numberWith... methods to create NSDecimalNumber objects. They are declared to return NSNumber objects and are not guaranteed to function as NSDecimalNumber instances.

This is explained in this thread by Bill Bumgarner, a developer at Apple. I would encourage you to file a bug against this behavior, referencing bug rdar://6487304.

As an alternative these are all of the appropriate methods to use to create an NSDecimalNumber:

+ (NSDecimalNumber *)decimalNumberWithMantissa:(unsigned long long)mantissa
                     exponent:(short)exponent isNegative:(BOOL)flag;
+ (NSDecimalNumber *)decimalNumberWithDecimal:(NSDecimal)dcm;
+ (NSDecimalNumber *)decimalNumberWithString:(NSString *)numberValue;
+ (NSDecimalNumber *)decimalNumberWithString:(NSString *)numberValue locale:(id)locale;

+ (NSDecimalNumber *)zero;
+ (NSDecimalNumber *)one;
+ (NSDecimalNumber *)minimumDecimalNumber;
+ (NSDecimalNumber *)maximumDecimalNumber;
+ (NSDecimalNumber *)notANumber;

If you simply want an NSDecimalNumber from a float or int constant try something like this:

NSDecimalNumber *dn = [NSDecimalNumber decimalNumberWithDecimal:
                             [[NSNumber numberWithFloat:2.75f] decimalValue];
Ashley Clark
  • 8,813
  • 3
  • 35
  • 35
  • 5
    Additionally, as @orj below said, it is also correct to use `-initWithFloat:`, `-initWithDouble:` and other methods. These are declared to return `id`, and I've just verified that they indeed return an `NSDecimalNumber` on Snow Leopard. – Ivan Vučica Sep 29 '11 at 08:55
  • 2
    +1 for mentioning `NSNumber`'s `decimalValue`. I totally missed that one in the docs. – Guillaume Algis Jun 12 '13 at 09:14
  • 3
    Maybe someone should change the title to "How to make something simple amazingly complicated". Thanks anyway for straighten things out. – turingtested Jan 08 '16 at 13:13
34

The correct way is actually to do this:

NSDecimalNumber *floatDecimal = [[[NSDecimalNumber alloc] initWithFloat:42.13f] autorelease];
NSDecimalNumber *doubleDecimal = [[[NSDecimalNumber alloc] initWithDouble:53.1234] autorelease];
NSDecimalNumber *intDecimal = [[[NSDecimalNumber alloc] initWithInt:53] autorelease];

NSLog(@"floatDecimal floatValue=%6.3f", [floatDecimal floatValue]);
NSLog(@"doubleDecimal doubleValue=%6.3f", [doubleDecimal doubleValue]); 
NSLog(@"intDecimal intValue=%d", [intDecimal intValue]);

See more info here.

orj
  • 13,234
  • 14
  • 63
  • 73
7

Design-wise, you should try to avoid converting NSDecimalNumber or NSDecimals to and from int, float, and double values for the same reasons it's recommended you use NSDecimalNumbers: loss of precision and binary floating point representation issues. I know, sometimes it's unavoidable (taking input from a slider, doing trigonometric calculations, etc.), but you should try to take input from users as NSStrings and then use initWithString:locale: or decimalNumberWithString:locale: to generate the NSDecimalNumbers. Do all your math with the NSDecimalNumbers and return their representation to users or save them to SQLite (or wherever) as their string description using descriptionWithLocale:.

If you have to input from an int, float, or double, you could do something like the following:

int myInt = 3;
NSDecimalNumber *newDecimal = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%d", myInt]];

or you could follow Ashley's suggestion to make sure you're safe in the decimal construction.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
0

One small addition: if you init NSDecimalNumber from a string, it might be useful to set a locale also. For example, if your string contains comma as a decimal separator.

self.order.amount = [NSDecimalNumber decimalNumberWithString:self.amountText locale:[NSLocale currentLocale]];
Denis Kutlubaev
  • 15,320
  • 6
  • 84
  • 70