1

My app remembers MPMediaItems by persistentId, as one does. It saves the persistentId in a JSON file, which is later read and parsed. NSJSONSerialization creates an NSDecimalNumber to contain the Id, which my app later uses in an MPMediaQuery to get the MPMediaItem.

What I found is that sometimes the wrong MPMediaItem is found. When I poked around some more, I discovered that when I convert the NSDecimalNumber to a uint64_t value, [NSNumber unsignedLongLongValue] returns the wrong value!

NSDecimalNumber* const songId = _songId;
uint64_t         const songIdValue = songId.unsignedLongLongValue;

songId is      1457249251113381177
songIdValue is 1457249251113381120
-or-
songId is      0x1439307919d12d39
songIdValue is 0x1439307919d12d00

What the heck? It looks like the low byte is getting cleared in the conversion of this NSDecimalNumber to a uint64_t. Is this somehow correct behavior for NSDecimalNumber of have I found a bug in it?

I'm working around this by converting the NSDecimalNumber from NSJSONSerialization to a string and converting the string to a uint64_t with [NSString longLongValue].

It's scary in that it seems I now have go and check EVERY place I store large integers in JSON files and make sure I evaluate them in this NSString-y way.

  • I found a comment here that says that NSDecimalNumber converts to a double before then converting to a uint64_t. Therefore, any significant bits beyond 53 are cleared, as I've seen here. Unbelievable that Apple lets this stand! NSJsonSerialization errs in choosing this class for integers rather than NSNumber. I'm going switch my JSON parsing to JSONKit, and I'd advise anyone else to do the same. – Christopher Schardt May 31 '16 at 14:23
  • In his comment Christopher may be referring to [this answer to *NSDecimalNumber and large unsigned long long (64-bit) integers*](http://stackoverflow.com/questions/6710469/nsdecimalnumber-and-large-unsigned-long-long-64-bit-integers/6713550#6713550) – CRD May 31 '16 at 16:32

1 Answers1

0

If you want to keep a maximum of precision, you can :

Work only with NSDecimalNumbers,

Use a NSString when you transfer the value of a NSDecimalNumber to another variable that is not a NSDecimalNumber. The methods like [NSDecimalNumber longLongValue] or [NSDecimalNumber doubleValue] are not specific enough.

NSDecimalNumber     *songId;
uint64_t            songIdValue;
NSString            *intermediateStr;

songId = [NSDecimalNumber decimalNumberWithString:@"1457249251113381177"];  
intermediateStr = songId.stringValue;
songIdValue = intermediateStr.longLongValue;

Why using strings is more precise than other methods ? I don't know. But songIdValue contains the right value without any loss.

Lionel_A
  • 550
  • 4
  • 12