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â€¦