5

Here is a simple code that shows what I think is a bug when dealing with double numbers...

double wtf = 36.76662445068359375000;
id xxx = [NSDecimalNumber numberWithDouble: wtf];
NSString *myBug = [xxx stringValue];
NSLog(@"%.20f", wtf);
NSLog(@"%@", myBug);
NSLog(@"-------\n");

the terminal will show two different numbers

36.76662445068359375000 and

36.76662445068359168

Is this a bug or am I missing something?

if the second number is being rounded, it is a very strange rounding btw...

= = = = = = = = = =

I am editing the original question to include one more WTF bug...

try this:

modify the original number and truncate it on 10 decimal digits... so...

double wtf = 36.76662445068359375000;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setMaximumFractionDigits:10];

NSString *valueX = [formatter stringFromNumber:[NSDecimalNumber numberWithDouble:wtf]];
[formatter release];
NSLog(@"%@", valueX);

the answer now is 36.7666244507

now the value is a string with 10 decimal digits... now lets convert it back to double

double myDoubleAgain = [valueX doubleValue];
NSLog(@"%.20f", myDoubleAgain);

the answer is 36.76662445070000018177 ??????

myDoubleAgain has now more digits!!!!

Duck
  • 34,902
  • 47
  • 248
  • 470
  • 1
    not a bug, read http://en.wikipedia.org/wiki/IEEE_754-1985 – Mauricio Scheffer Mar 19 '10 at 17:14
  • I wonder how many platforms there are where this still will be asked. .net: http://stackoverflow.com/questions/566958/double-precision-problems-on-c-net JS and perl: http://stackoverflow.com/questions/2387675/why-81-66-15-1224-8999999-in-javascript-or-perl-and-not-1224-9 python: http://stackoverflow.com/questions/2284873/float-inaccuracies-in-python C: http://stackoverflow.com/questions/1853734/a-floating-point-array-in-c Java: http://stackoverflow.com/questions/1661273/java-floating-point-arithmetic – Mauricio Scheffer Mar 19 '10 at 17:17
  • 5
    Real bug and not a dup. See my answer. – Stephen Canon Mar 19 '10 at 17:47
  • Not a bug at all, but misuse of an API without considering the implications. See http://www.cimgf.com/2008/04/23/cocoa-tutorial-dont-be-lazy-with-nsdecimalnumber-like-me/ and particularly the comments. Marcus is a well respected member of the community. – David Schaefgen Mar 19 '10 at 18:10
  • I have added more code to my question proving it is a bug. A simple conversion from string to double and vice-versa should not have any rounding. If you have a double with zillion of digits and you want a string, the string should be a copy of those same digits. No conversion necessary, just copy and the copy is not being done as expected. Strings support more characters as double, so a regular copy of digits should be done. Converting from string to double could generate a truncate, if you have more "digits" on your string than the double can support. – Duck Mar 19 '10 at 18:25
  • 2
    @Mike: The new material you have added is unrelated to the original issue (a problem with the import of a double into NSDecimalNumber). It is a side-effect of trying to represent decimals as floating-point numbers. See the references that Mauricio and Nathan link to. – Brad Larson Mar 19 '10 at 18:28
  • Wow, my mistake. My apologies for assuming things. I'm glad this one's different :) – Mauricio Scheffer Mar 19 '10 at 18:29
  • @Mike: What Brad said. The new issue you're seeing is just the usual issue of `36.7666244507` being (correctly) rounded to the closest representable floating-point number. – Stephen Canon Mar 19 '10 at 18:34

2 Answers2

10

Normally, I'm the one who comes in and explains to people that the number they entered is not representable as a floating-point number, and where the rounding errors are, blah blah blah.

This question is much more fun than what we usually see, and it illustrates exactly what's wrong with the crowd wisdom of "floating point is inexact, read 'what every computer scientist should know...' lolz".

36.76662445068359375 is not just any 19-digit decimal number. It happens to be a 19 digit decimal number that is also exactly representable in double precision binary floating point. Thus, the initial conversion implicit in:

double wtf = 36.76662445068359375000;

is exact. wtf contains exactly b100100.11000100010000011, and no rounding has occurred.

The spec for NSDecimalNumber says that it represents numbers as a 38 digit decimal mantissa and a decimal exponent in the range [-127,128], so the value in wtf is also exactly representable as an NSDecimalNumber. Thus, we may conclude that numberWithDouble is not delivering a correct conversion. Although I cannot find documentation that claims that this conversion routine is correctly rounded, there is no good reason for it not to be. This is a real bug, please report it.

I note that the string formatters on iPhoneOS seem to deliver correctly rounded results, so you can probably work around this by first formatting the double as a string with 38 digit precision and then using decimalNumberWithString. Not ideal, but it may work for you.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
  • You might have a point if he were not using numberWithDouble. That message is not defined for NSDecimalNumber but for its super class NSNumber. There is no API for NSDecimalNumber that offers convenience creation of an instance directly from a double. – David Schaefgen Mar 19 '10 at 18:05
  • 1
    Nice detective work on that. If I can create a reproducible test case for this, I'll file a bug report. – Brad Larson Mar 19 '10 at 18:05
  • Thanks, I have already filled a bug and I have another proof of it. I will include in my original question. – Duck Mar 19 '10 at 18:10
  • @David Schaefgen: one would hope that NSDecimalNumber would provide its own implementation of numberWithDouble that overrides the NSNumber implementation. – Stephen Canon Mar 19 '10 at 18:16
  • 1
    @David Schaefgen: Even if it just returned an NSNumber, not an NSDecimalNumber, the NSNumber should be able to properly represent this particular double and log it back out. Something is going wrong in the import here. – Brad Larson Mar 19 '10 at 18:20
  • I have added more code to my original question to prove it is a bug. A simple conversion string and double generates more digits – Duck Mar 19 '10 at 18:30
2

Trying to go beyond roughly 16 digits of decimal precision with a double is generally not recommended. Frankly, I'm surprised that you are able to represent this double (with 19 significant digits) in a way that maintains that precision when logging it out. You may even get different behavior on the iPhone itself, which maps a long double type to just plain double (your Mac may be handling this as a long double behind the scenes).

The rounding you are seeing may be happening at the binary level (see here for more on this), so you won't be seeing the decimal rounding that you are expecting.

It is for these reasons that you will want to work entirely with NSDecimalNumbers or NSDecimals from start to finish if you need this kind of high-precision math. To do this, do not convert to and from floating point types, but instead use NSStrings directly to populate and export the numbers (or store them as NSDecimalNumbers in Core Data).

For example, you could work around the above problems with the following code:

id xxx = [NSDecimalNumber decimalNumberWithString:@"36.76662445068359375000"];

NSDecimalNumbers (and their C struct equivalent NSDecimal) can handle up to 38 significant digits.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571