Why is longLongValue returning the incorrect value

2020-04-11 04:41发布

问题:

I have a NSDictionary that contains a key with a value of 4937446359977427944. I try and get the value of it as a long long and get 4937446359977427968 back?

NSLog(@"value1 = %@", [dict objectForKey"MyKey"]); // prints 4937446359977427944 

long long lv = [dict objectForKey:@"MyKey"] longLongValue];

NSLog(@"value2 = %lld", lv); // prints 4937446359977427968

Doing:

NSLog(@"%lld", [@"4937446359977427944" longLongValue]); // prints 4937446359977427944

I'm assuming it is some kind of round off issue since the lower bits seems to be cleared, I just don't know how to stop it (or why it's happening).

The dictionary is being created using NSJSONSerialization and the JSON object does (correctly) contain a "MyKey": 4937446359977427944 entry and the dict object is correct.

The value being held in the NSDictionary is a NSDecimalNumber

Is something being convert to a float behind the scenes?

回答1:

NSDecimalValue is not stored as a double, it's a 64 bits unsigned integer mantissa, an 8 bit signed integer exponent of base 10, and a sign bit.

The problem is that an exact value of an NSDecimalValue is only representable as ... an NSDecimalValue.

You can get an approximate 64 bits IEE754 value with method doubleValue.

When you try to use longLongValue you effectively get the result of casting to a long long int the approximate IEE754 value.

You may or may not consider it a bug in the implementation of NSDecimalValue (and eventually file a radar and ask Apple to use a different conversion routine). But strictly speaking this is not a bug: it's a design decision.

You should think of NSDecimalValue as a sort of floating point decimal. In fact it's very similar to a software implementation of what IEEE754 would call an extended precision floating point decimal number, except that it does not conform to that definition (because it does not have an exponent supporting at least values between −6143 and +6144 and because it does not support NANs and infinites).

In other words, it's not an extended implementation of an integer, it's an extended (but lacking NANs and infinites) implementation of a double. The fact that Apple natively only provides an approximate conversion to double (implying that the conversion to long long int may or may not be exact for any value that exceed 53 bits of precision) is not a bug.

You may or may not want to implement a different conversion yourself (with a category).

Another possible point of view is to consider the problem being a bug in the JSon implementation you used. But this is also highly debatable: it gave you a NSDecimalValue and that's arguably a correct representation. Either you operate with the NSDecimalValue or you are responsible for any conversion of it.



回答2:

I'm not sure if your are interested in a simple solution or just looking into the details of why the loss of precision takes place.

If you are interested in a simple answer: -[NSDecimalNumber description] products a string with the value, and -[NSString longLongValue] converts a string into a long long

NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithString:@"4937446359977427944"];
long long longLongNumber = [[decimalNumber description] longLongValue];
NSLog(@"decimalNumber %@ -- longLongNumber %lld", decimalNumber, longLongNumber);

outputs

2014-04-16 08:51:21.221 APP_NAME[30458:60b] decimalNumber 4937446359977427944 -- longLongNumber 4937446359977427944

Final Note

[decimalNumber descriptionWithLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]] may be more reliable is your app supports multiple locales.



回答3:

For anyone interested in quick solution to the problem, as per Analog File proper answer:

long long someNumber = 8204064638523577098;
NSLog(@"some number lld:               %lld", someNumber);
NSNumber *snNSNumber = [NSNumber numberWithLongLong:someNumber];
NSLog(@"some number NSNumber:          %@", snNSNumber);
NSString *someJson = @"{\"someValue\":8204064638523577098}";
NSDictionary* dict = [NSJSONSerialization
 JSONObjectWithData:[someJson dataUsingEncoding:NSUTF8StringEncoding]
 options:0
 error:nil];
NSLog(@"Dict: %@", dict);
NSLog(@"Some digit out of dict:        %@", [dict objectForKey:@"someValue"]);
NSLog(@"Some digit out of dict as lld: %lld", [[dict objectForKey:@"someValue"] longLongValue]);
long long someNumberParsed;
sscanf([[[dict objectForKey:@"someValue"] stringValue] UTF8String], "%lld", &someNumberParsed);
NSLog(@"Properly parsed lld:           %lld", someNumberParsed);

Results in:

2014-04-16 14:22:02.997 Tutorial4[97950:303] some number lld:
8204064638523577098

2014-04-16 14:22:02.998 Tutorial4[97950:303] some number NSNumber:
8204064638523577098

2014-04-16 14:22:02.998 Tutorial4[97950:303] Dict: { someValue = 8204064638523577098; }

2014-04-16 14:22:02.998 Tutorial4[97950:303] Some digit out of dict:
8204064638523577098

2014-04-16 14:22:02.999 Tutorial4[97950:303] Some digit out of dict as lld: 8204064638523577344

2014-04-16 14:22:02.999 Tutorial4[97950:303] Properly parsed lld:
8204064638523577098



标签: objective-c