Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSNumber double with many decimal places being rounded/truncated

I have a double in an NSNumber.

double myDouble = 1363395572.6129999;

NSNumber *doubleNumber = @(myDouble); 
// using [NSNumber numberWithDouble:myDouble] leads to the same result

This is where it gets problematic.

doubleNumber.doubleValue seems to return the correct and full value (1363395572.6129999)

However, looking at doubleNumber in the debugger or doing doubleNumber.description gives me (1363395572.613).

I would understand if perhaps this was just some display formatting, but when I then stick this object into a JSON payload, the messed up rounded value gets inserted instead of the actual number.

The way I'm doing this is something like this:

NSData *jsonData = [NSJSONSerialization dataWithJSONObject:(Dictionary containing NSNumber)
                                                           options:0 error:nil];

NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

Looking at the string at this point shows me the truncated number with 3 decimal places even though the NSNumber I inserted had 7.

My question is why is this happening and more importantly how can I stop it from happening?

EDIT with conclusion:

For anyone who stumbles onto this, the problem was not clear to me from the beginning but the actual issue is that NSNumber and double are both incapable of holding a number with the sort of precision I am looking for. As Martin's answer shows, my problem occurred as soon as I deserialized the initial number values from a JSON response.

I ended up working around my problem by reworking the whole system to stop depending on this level of precision(since these are timestamps, microseconds) of these numbers on the client, and instead use a different identifier to pass around with the API.

As Martin and Leo pointed out, in order to get around this problem one would need to use a custom JSON parser that allows parsing of a JSON number into an NSDecimalNumber rather than an NSNumber. A better solution to my problem in particular was what I outlined in the previous paragraph, so I did not pursue this route.

like image 714
Dima Avatar asked Dec 09 '22 08:12

Dima


1 Answers

As already said in above comments, the precision of double is about 16 decimal digits. 1363395572.612999 has 17 digits, and converting this decimal number to double gives exactly the same results as for 1363395572.613:

double myDouble = 1363395572.6129999;
double myDouble1 = 1363395572.613;

NSLog(@"%.20f", myDouble);  // 1363395572.61299991607666015625
NSLog(@"%.20f", myDouble1); // 1363395572.61299991607666015625
NSLog(@"%s", myDouble == myDouble1 ? "equal" : "different"); // equal

Therefore, within the precision of double, the output 1363395572.613 is correct.

If your goal is to send precisely the number "1363395572.6129999" then you cannot store it in a double first because that already looses the precision. A possible solution would be to use NSDecimalNumber (which has a precision of 38 decimal digits):

NSDecimalNumber *doubleNumber = [NSDecimalNumber decimalNumberWithString:@"1363395572.6129999"];
NSDictionary *dict = @{@"key": doubleNumber};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
                                                   options:0 error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
// {"key":1363395572.6129999}

Example with long double and NSDecimalNumber:

long double ld1 = 1363395572.6129999L;
long double ld2 = 1363395572.613L;

NSDecimalNumber *num1 = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%.7Lf", ld1]];
NSDecimalNumber *num2 = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%.7Lf", ld2]];

NSDictionary *dict = @{@"key1": num1, @"key2": num2};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
                                                   options:0 error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
// {"key1":1363395572.6129999,"key2":1363395572.613}

Update: As it turned out in the discussion, the problem occurs already when the data is read from a JSON object sent by a server. The following example shows that NSJSONSerialization is not able to read floating point numbers with more than "double" precision from JSON data:

NSString *jsonString = @"{\"key1\":1363395572.6129999,\"key2\":1363395572.613}";
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict2 = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
NSNumber *n1 = dict2[@"key1"];
NSNumber *n2 = dict2[@"key2"];

BOOL b = [n1 isEqualTo:n2]; // YES
like image 164
Martin R Avatar answered Dec 10 '22 21:12

Martin R