NSNumberFormatter and Objective-C Types

 ·  iOS, Development  ·  Tagged NSNumberFormatter, multiplier, Objective-C types, gotcha and bug

On a recent project, I ran into some very unexpected behavior with NSNumberFormatter. I was working with an API that delivered price information in cents. The price needed to be displayed in dollars. I decided to store the value in cents to maintain consistency with the API and to use NSNumberFormatter to generate the string for the UI. It seemed like a great solution.

Here's how it worked:

// Ninety-nine dollars in cents (this comes from the API)
NSNumber *numberInCents = @9900;

NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];

[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setMultiplier:@0.01];

NSString *formattedString = [numberFormatter stringFromNumber:numberInCents];

This generated the string $99.00. So far, so good.

For this particular project, one of the requirements was to remove the fractional part of the formatted number if it was zero. I got that working, but I wanted to make sure that the fractional part was still displayed when necessary. To test it, I changed the value of numberInCents to @9999. The result? $99.00. What was going on?

After much confusion, I came across a question from someone with the same problem. The issue was that the Objective‑C type of numberInCents was i which stands for int. You can get the Objective-C type from any NSValue—the superclass of NSNumber—with the method ‑[NSValue objCType]. For some reason, NSNumberFormatter takes this into account when applying the multiplier. The behavior is similar to what you would get when doing an integer division: 9999 / 100 = 99.

The workaround was to make sure that the Objective‑C type of the number was always a floating-point type:

// Ninety-nine dollars, ninety-nine cents in cents (this comes from the API)
NSNumber *numberInCents = @9999;

NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];

[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setMultiplier:@0.01];

// Ensure that the number to format has a floating-point Objective-C type
NSNumber *floatingPointNumberInCents = @([numberInCents floatValue]);
NSString *formattedString = [numberFormatter stringFromNumber:floatingPointNumberInCents];

This gave the expected result: $99.99.

It is not clear whether this is the intended behavior for NSNumberFormatter, so I filed a radar (rdar://16775214) to request clarification. Fortunately for now, the workaround is straightforward. Have you encountered any similar issues with NSNumberFormatter? Let me know @a_hershberger on Twitter.