Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSDecimalNumber and large unsigned long long (64-bit) integers

I'm handling large 64 bit unsigned integers from a JSON source which are being parsed into NSDecimalNumbers, which is apparently to "faithfully represent arbitrary-precision numbers".

The problem I'm having is that I cant get the correct numbers out of this class. For example (using the largest value possible):

print (unsigned long long) [[NSDecimalNumber decimalNumberWithString:@"18446744073709551615"] unsignedLongLongValue]
= 0 // Incorrect
print (unsigned long long) [[NSDecimalNumber decimalNumberWithString:@"9223372036854775808"] unsignedLongLongValue]
= 9223372036854775808 // Correct
print (unsigned long long) [[NSDecimalNumber decimalNumberWithString:@"9223372036854775810"] unsignedLongLongValue]
= 9223372036854775808 // Incorrect

It would appear that I can't get anything larger than the maximum signed long long value out of an NSDecimalNumber. It's not liking values larger than 9223372036854775808. However it seems that the number is being stored in the full precision within an NSDecimalNumber, as:

po [[NSDecimalNumber decimalNumberWithString:@"18446744073709551615"] stringValue]
= 18446744073709551615

I've noticed that NSNumber objects can handle these large numbers fine and unsignedLongLongValue works correctly. It's just NSDecimalNumbers (which I am stuck with) which aren't working.

How can I get correct unsigned long long values out of NSDecimalNumbers? Or at least convert them into NSNumber objects where unsignedLongLongValue does work.

like image 902
Michael Waterfall Avatar asked Jul 15 '11 16:07

Michael Waterfall


2 Answers

I've had a response from Apple on this issue via the developer forums:

This is a standing issue with NSDecimalNumber where the simple accessors (such as [unsigned]longLongValue) go through the doubleValue accessor – this any value requiring more than 53 bits of precision will be inappropriately rounded. Feel free to Report a Bug about this issue and mention bug number 8220543.

That said, if you are just getting 64-bit numbers from JSON, you should be able to just use NSNumber instead of NSDecimalNumber.

So I'm solving it by changing parser from SBJson to JSONKit, which is not only much faster, it also maps numbers into NSNumber objects instead of NSDecimalNumber objects.

like image 63
Michael Waterfall Avatar answered Sep 22 '22 20:09

Michael Waterfall


If you still want to extract an unsigned long long value from an NSDecimalNumber, you could use the approach suggested by johne here and do something like the following:

NSDecimalNumber *testNumber = [NSDecimalNumber decimalNumberWithString:@"18446744073709551615"];
unsigned long long ullvalue = strtoull([[testNumber stringValue] UTF8String], NULL, 0);
NSLog(@"Number:%llu", ullvalue);

which produces the proper result of

Number:18446744073709551615

I tried to do this using an NSScanner:

NSScanner *theScanner = [[NSScanner alloc] initWithString:[testNumber stringValue]];
unsigned long long outputValue;

[theScanner scanLongLong:(long long *)&outputValue];
[theScanner release];

but unfortunately, it only reads the signed long long values, so the above gives the incorrect value of 9223372036854775807.

like image 31
Brad Larson Avatar answered Sep 21 '22 20:09

Brad Larson