Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behaviour formatting very large decimal numbers in ObjC

Tags:

objective-c

I am running into unexpected behaviour formatting very large numbers in ObjC using the NSNumberFormatter.

It seems that the number formatter rounds decimals (NSDecimalNumber) after the fifteenth digit regardless of fraction digits.

The below test fails on values 1,3 and 5.

Two requests:

  • Any suggestions on alternative code would be greatly appreciated?
  • I assume the issue is happening due to the usage of a hard-coded digit limit in NSNumberFormatter?

The post here lists a workaround without sufficient description if the problem. Also our application (banking sector) runs across multiple countries and we link the formatting to the user's locale as configured in the backend. This workaround would imply that we write our own number formatter to handle the requirement. Something I do not want to do.

- (void)testFormatterUsingOnlySDK {
    NSDecimalNumber *value1 = [NSDecimalNumber decimalNumberWithMantissa: 9423372036854775808u exponent:-3 isNegative:YES];
    NSDecimalNumber *value2 = [NSDecimalNumber decimalNumberWithMantissa: 9999999999999990u exponent:-3 isNegative:YES];
    NSDecimalNumber *value3 = [NSDecimalNumber decimalNumberWithMantissa: 9999999999999991u exponent:-3 isNegative:YES];
    NSDecimalNumber *value4 = [NSDecimalNumber decimalNumberWithMantissa: 99999999999999900u exponent:-4 isNegative:YES];
    NSDecimalNumber *value5 = [NSDecimalNumber decimalNumberWithMantissa: 11111111111111110u exponent:-4 isNegative:YES];

    NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
    formatter.allowsFloats = YES;
    formatter.maximumFractionDigits = 3;

    [self assertStringAreEqualWithActual:[formatter stringFromNumber:value1] andExpeted: @"-9423372036854775.808"];
    [self assertStringAreEqualWithActual:[formatter stringFromNumber:value2] andExpeted: @"-9999999999999.99"];
    [self assertStringAreEqualWithActual:[formatter stringFromNumber:value3] andExpeted: @"-9999999999999.991"];
    [self assertStringAreEqualWithActual:[formatter stringFromNumber:value4] andExpeted: @"-9999999999999.99"];
    [self assertStringAreEqualWithActual:[formatter stringFromNumber:value5] andExpeted: @"-1111111111111.111"];
}

- (void)assertStringAreEqualWithActual:(NSString *)actual andExpeted:(NSString *)expected {
    STAssertTrue([expected isEqualToString:actual], @"Expected %@ but got %@", expected, actual);
}
like image 450
amok Avatar asked Dec 19 '12 13:12

amok


1 Answers

Unfortunately, NSNumberFormatter doesn't work correctly with NSDecimalNumber. The problem (very probably) is that the first thing it does is calling doubleValue on the number it wants to format.

See also NSDecimalNumber round long numbers

After many tries with NSNumberFormatter, I have created my own formatter, it's actually very easy:

  1. Handle NaN.
  2. Round using roundToScale:
  3. Get stringValue
  4. Check if negative, remove leading -
  5. Find decimal point (.)
  6. Localize decimal point ([locale objectForKey:NSLocaleDecimalSeparator])
  7. Add grouping separators ([locale objectForKey:NSLocaleGroupingSeparator])
  8. If negative, add leading - or put the number into parenthesis if you are formatting currency.
  9. Done.
like image 176
Sulthan Avatar answered Nov 25 '22 14:11

Sulthan