Cocoa Tutorial: Don’t Be Lazy With NSDecimalNumber (Like Me)
NSDecimalNumber is Objective-Câ€™s solution to numbers that need to be very precise. The documentation defines it as:
NSDecimalNumber, an immutable subclass of NSNumber, provides an object-oriented wrapper for doing base-10 arithmetic. An instance can represent any number that can be expressed as mantissa x 10^exponent where mantissa is a decimal integer up to 38 digits long, and exponent is an integer from â€“128 through 127.
If you are dealing with currency at all, then you should be using NSDecimalNumber. However, since it is immutable and definitely not a primitive then it is difficult to use right? Well â€” yes â€” a bit. But if you do not want to see your $9.50 item displayed as $9.49999994 or something then you are better off using NSDecimalNumber right from the beginning. Otherwise you are going to be converting to it later and that is a LOT more painful.
To create an NSDecimalNumber there are a few helper class methods:
The two methods that I ended up using the most are +decimalNumberWithDecimal: and +decimalNumberWithMantissa:exponent:isNegative: If you already have the number stored as an NSNumber then
[NSDecimalNumber decimalNumberWithDecima:[yourNumber decimalValue]]
is the easiest way to convert it. If the number is a primitive then
[NSDecimalNumber decimalNumberWithMantissa:(yourPrimitive * precision) exponent:-(precision) isNegative:(yourPrimitive < 0 ? YES : NO)] will convert it.
So now you have your number as an NSDecimalNumber. How do you do anything with it? Specifically how do you add, divide, multiple and subtract an immutable number? Fortunately the class has methods to handle all of these:
With these methods you can do all of the math functions and create a new NSDecimalNumber with each call. Naturally these can even be chained together to make a deliciously convoluted method call.
So what happens when you need to control the decimal precision of an NSDecimalNumber? Specifically what happens when you multiply 9.49 * 10% and what only two decimal points left over? That is where the NSDecimalNumberBehaviors come in. The NSDecimalNumberBehaviors protocol is defined as:
The NSDecimalBehaviors protocol declares three methods that control the discretionary aspects of working with NSDecimalNumber objects.
The scale and roundingMode methods determine the precision of NSDecimalNumberâ€™s return values and the way in which those values should be rounded to fit that precision. The exceptionDuringOperation:error:leftOperand:rightOperand: method determines the way in which an NSDecimalNumber object should handle different calculation errors.
For an example of a class that adopts the NSDecimalBehaviors protocol, see the specification for NSDecimalNumberHandler.
This protocol is implemented in the NSDecimalNumberHandler class. By constructing an NSDecimalNumberHandler and passing it in as part of the math call you can control the rounding applied to the math function. Even better, you can reuse the handler class as often as you want. Therefore to perform the calculation above:
NSDecimalNumber *price = [NSDecimalNumber decimalNumberWithMantissa:949 exponent:-2 isNegative:NO];â€¨ NSDecimalNumber *percent = [NSDecimalNumber decimalNumberWithMatissa:10 exponent:-2 isNegative:NO];â€¨ NSDecimalNumberHandler *handler = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:-2 raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO]; NSDecimalNumber *result = [price decimalNumberByMultiplyingBy:percent withBehavior:handler];
Note that instead of using -decimalNumberByMultiplyingBy: I used a similar method which adds withBehavior: on the end of it. Each of the math functions listed above includes a companion method which allows you to pass in the behavior. Also note that the NSDecimalNumberHandler class allows you to raise exceptions in several circumstances. You can review Appleâ€™s documentation on each of these exceptions if you find a need for them.
So how does Core Data handle NSDecimalNumber(s)? In a word â€” perfectly. In your Core Data model simply define the attribute as â€œdecimalâ€ instead of double or another primitive and you can store the NSDecimalNumber directly in the repository and retrieve it as an NSDecimalNumber. You can also retrieve it as an NSNumber if needed since NSDecimalNumber is a subclass of NSNumber.
All of the number formatters will handle NSDecimalNumber perfectly with no loss of precision. If you pass it to a currency formatter you will get back the number you expect unlike passing a double and hoping for the best.
So is NSDecimalNumber harder to use than primitives? Absolutely. Compared to primitives it is a lot harder to code, maintain and read. Is it worth it? Depends.
If you are dealing with currency then there is no question â€” use NSDecimalNumber and avoid doubles like the plague. Spend the time, learn the API. Otherwise you will end up having to migrate over to them later when you discover that $9.49 * 10% may not equal $0.95.
If you are not dealing with currency or another number that requires absolute precision (such as screen drawing, et al) then you probably do not need to deal with the pain.
So why did I title this article with the words â€œLike Meâ€? Take a good guessâ€¦
Thanks for the tip!
Great blog BTW. I recently started programming with Cocoa and I have found CIMGF to be a valuable resource. Keep it up and I will keep reading!
NSDecimalNumber also supports the various +numberWith(Primitive): methods that come with NSNumber. And yes, they correctly return an NSDecimalNumber.
Since NSDecimalNumber *test = [NSDecimalNumber numberWithDouble:9.5]; will produce a warning it is not recommended. When dealing with NSDecimalNumber objects it is much safer to use the recognized initializers and avoid the ones from its NSNumber parent class.
Ah, OK. In that case -initWithDouble: and friends are still valid initializers (with no warnings), and they of course give you NSDecimalNumbers.
On the other hand, for doing base-10 arithmetic, you’re probably right not to use -initWithDouble:, since NSDecimalNumber’s internal representation uses base-10 exponents, while primitive floats and doubles of course use base-2 exponents, and there could be some (slight) loss of precision.
For integers, though, I’d still prefer [[NSDecimalNumber alloc] initWithInteger:100] to [[NSDecimalNumber alloc] initWithMantissa:1 exponent:2 isNegative:NO].
I would not recommend even doing that. There is a reason that the NSDecimalNumber is configured differently and has an unusual constructor. Ignoring that seems foolish just to save a couple of characters typing.
Definitely sounds like the wrong kind of lazy to me :)
I cam across this article just as I was writing a small app the deals with currency as well. It seems that historically wisdom has said to always convert any currency value to a whole integer before doing any calculations with, does NSDecimalNumber alleviate the need for doing this?
Yes they are designed for currency use and the proper rounding of decimal points. They solve the issues with floating point math.
In converting primitives to the mantissa/exponent format, you mention precision. Is this the same value as the scale in NSDecimalNumberBehaviors, or is it a printf-style float-precision specifier (like 4.3 for 4 places before the decimal point and 3 after)?
Nevermind, I figured it out. The precision refers to the number of digits after the decimal point and the calculation of the mantissa should read:
mantissa = (yourPrimitive * 10^precision)
Thanks for this informative post.