Unexpected formatting behavior for very large decimal numbers in ObjC

I run an unexpected behavior by formatting very large numbers in ObjC using NSNumberFormatter.

It seems that the formatter number rounds decimal numbers (NSDecimalNumber) after the fifteenth digit, regardless of the digits of the digits.

The next test fails with values โ€‹โ€‹of 1.3 and 5.

Two queries:

  • Any suggestions for an alternative code are welcome.
  • I assume the problem is due to using a hard limit in NSNumberFormatter?

The message here indicates a workaround without a sufficient description if the problem. Also, our application (banking sector) works in several countries, and we bind formatting to the user's language, as indicated in the backend. This workaround implies that we will write our own format format to handle this 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); } 
+6
source share
3 answers

Unfortunately, NSNumberFormatter does not work properly with NSDecimalNumber . The problem (very likely) is that the first thing she does is call doubleValue for the number he wants to format.

See also NSDecimalNumber Round Long Numbers.

After many attempts with NSNumberFormatter , I created my own formatter, in fact it is very simple:

  • Pen NaN .
  • Round using roundToScale:
  • Get stringValue
  • Check, not negative, remove the lead -
  • Find the decimal point ( . )
  • Localize the decimal point ( [locale objectForKey:NSLocaleDecimalSeparator] )
  • Add grouping delimiters ( [locale objectForKey:NSLocaleGroupingSeparator] )
  • If this is negative, add the leading one - or enter the number in brackets if you format the currency.
  • Done.
+4
source

You must compile your own NSNumberFormatter from this open source code by changing the prefix. This should allow you to debug formatting and understand why this is happening. In the worst case scenario, you can submit an Apple patch.

http://code.google.com/p/cocotron/source/browse/Foundation/NSNumberFormatter.m?r=7542c3a7ef0ef75479e6154a75d304113f5a9738

+1
source

You set maximumFractionDigits to two. All failed tests contain three digits in the expected value. Either expectations or the code needs to be changed to match. If I make this change:

 formatter.maximumFractionDigits = 3; 

then all your test cases are executed.

+1
source

All Articles