Search code examples
iosjsonnsdecimalnumber

Very big ID in JSON, how to obtain it without losing precision


I have IDs in JSON file and some of them are really big but they fit inside bounds of unsigned long long int.

"id":9223372036854775807,

How to get this large number from JSON using objectForKey:idKey of NSDictionary? Can I use NSDecimalNumber? Some of this IDs fit into regular integer.


Solution

  • Tricky. Apple's JSON code converts integers above 10^18 to NSDecimalNumber, and smaller integers to plain NSNumber containing a 64 bit integer value. Now you might have hoped that unsignedLongLongValue would give you a 64 bit value, but it doesn't for NSDecimalNumber: The NSDecimalNumber first gets converted to double, and the result to unsigned long long, so you lose precision.

    Here's something that you can add as an extension to NSNumber. It's a bit tricky, because if you get a value very close to 2^64, converting it to double might get rounded to 2^64, which cannot be converted to 64 bit. So we need to divide by 10 first to make sure the result isn't too big.

    - (uint64_t)unsigned64bitValue
    {
        if ([self isKindOfClass:[NSDecimalNumber class]])
        {
            NSDecimalNumber* asDecimal = (NSDecimalNumber *) self;
            uint64_t tmp = (uint64_t) (asDecimal.doubleValue / 10.0);
    
            NSDecimalNumber* tmp1 = [[NSDecimalNumber alloc] initWithUnsignedLongLong:tmp];
            NSDecimalNumber* tmp2 = [tmp1 decimalNumberByMultiplyingByPowerOf10: 1];
            NSDecimalNumber* remainder = [asDecimal decimalNumberBySubtracting:tmp2];
    
            return (tmp * 10) + remainder.unsignedLongLongValue;
        }
        else
        {
            return self.unsignedLongLongValue;
        }
    }