iphoneccocoa-touchinexact-arithmetic

DBL_MAX loses significant part of its precision after being re-parsed from string


I'm running this code on my iPhone:

double d = DBL_MAX;
NSString *s = [NSString stringWithFormat:@"%.0f", d];
double dp = atof([s cStringUsingEncoding:[NSString defaultCStringEncoding]]);
NSString *pe = d == dp ? @"YES" : @"NO";

double one = 1;
double dpd = dp / one;
NSString *de = d == dpd ? @"YES" : @"NO";

NSLog(@"### Parsed are equal: %@, divided are equal: %@", pe, de);
NSLog(@"D   : %.0f", d);
NSLog(@"DP  : %.0f", dp);
NSLog(@"DPD : %.0f", dpd);

...and getting this output:

### Parsed are equal: NO, divided are equal: NO
D   : 17976931348623157081452742373170435679807056752584499659891747680315726078002853876058955863276687817154045895351438246423432132688946418276846754670353751698604991057655128207624549009038932894407586850845513394230458323690322294816580855933212334827479
DP  : 17976931348623155723920577891946972866121062246621938439403251722449088432276750723756897307653964877256701669823356283705419341284625019355047863102662518251134787976961389628366367996124520722972986881016593281354069269901878996004952428787693676134400
DPD : 17976931348623155723920577891946972866121062246621938439403251722449088432276750723756897307653964877256701669823356283705419341284625019355047863102662518251134787976961389628366367996124520722972986881016593281354069269901878996004952428787693676134400

Why a sequence of printf()/atof() loses precision (I assume stringWithFormat does printf internally)? It happens not only for DBL_MAX but for every significantly large number (i.e. for 10000 it works as expected, for DBL_MAX / 2 it does not). Is there a way to avoid it?


Solution

  • Not all decimals can be represented in binary. For example 0.2(dec) = 0.001100110011...(bin). Therefore when number is converted from decimal string it's sometimes truncated (or rounded).

    When converting from binary to decimal, even though it's always possible, the result sometimes is longer than n*log_10(2), where n is number of binary digits. For example 0.001(bin) = 0.125(dec), but 3*log_10(2)=0.903... Therefore when number is converted from binary to digital string, it's sometimes truncated as well.

    That's the reason why you get result that slightly differs.

    Here is an example. Suppose your mantissa is 6 digit. Let's convert the number 0.001111(bin) to decimal. The precise result is 0.234375, but this number is rounded to 0.23 because you need only 6*log_10(2)=1.8061 digits to represent any 6-digit binary. In this case 1.8061 is even rounded up to 2.

    Now lets see what we will get if we convert our 0.23 back to binary. It's 0.0011101011... This has to be rounded and result could be 0.001110 or 0.001111 depending on the way rounding is done.