I'm looking for the best way to instantiate an NSDecimalNumber from a double or short. There are the following NSNumber class and instance methods...
+NSNumber numberWithFloat +NSNumber numberWithDouble -NSNumber initWithFloat -NSNumber initWithDouble
but these appear to return NSNumber. On the other side, NSDecimalNumber defines the following:
+NSDecimalNumber decimalNumberWithMantissa:exponent:isNegative: +NSDecimalNumber decimalNumberWithDecimal:
There are a few possibilities here on which way to go. Xcode generates a warning if you have a NSDecimalNumber set to the return value of the NSNumber convenience methods above.
Would appreciate input on the cleanest and correct way to go...
Float and double Double is more precise than float and can store 64 bits, double of the number of bits float can store. Double is more precise and for storing large numbers, we prefer double over float. For example, to store the annual salary of the CEO of a company, double will be a more accurate choice.
If high precision is not required and the program only needs a huge array of decimal numbers to be stored, float is a cost-effective way of storing data and saves memory. Double is costlier, occupies more space and is more effective when more precision is required.
We can also print a float as a double and vice versa, it all depends on how we write the printf statement. Writing %f will strip off some significant digits, while when we specify number of digits, the entire value up till that will be printed. To print the value in exponential terms, you should use “%e”.
example obj3 (obj1): This line is invoking copy constructor and creates a new object obj3 that is a copy of object obj1. example *obj4 = new example (10): This is the way of instantiating an object that has dynamic storage duration.
You were on the right track (with caveats, see below). You should be able to just use the initialisers from NSNumber as they are inherited by NSDecimalNumber.
NSDecimalNumber *floatDecimal = [[[NSDecimalNumber alloc] initWithFloat:42.13f] autorelease]; NSDecimalNumber *doubleDecimal = [[[NSDecimalNumber alloc] initWithDouble:53.1234] autorelease]; NSDecimalNumber *intDecimal = [[[NSDecimalNumber alloc] initWithInt:53] autorelease]; NSLog(@"floatDecimal floatValue=%6.3f", [floatDecimal floatValue]); NSLog(@"doubleDecimal doubleValue=%6.3f", [doubleDecimal doubleValue]); NSLog(@"intDecimal intValue=%d", [intDecimal intValue]);
More info on this subject can be found here.
However, I've seen a lot of discussion on Stack Overflow and around the web about issues with initialising NSDecimalNumber. There seems to be some issues relating to precision and conversion to/from doubles with NSDecimalNumber
. Especially when you use the initialisation members inherited from NSNumber.
I knocked up the test below:
double dbl = 36.76662445068359375000; id xx1 = [NSDecimalNumber numberWithDouble: dbl]; // Don't do this id xx2 = [[[NSDecimalNumber alloc] initWithDouble: dbl] autorelease]; id xx3 = [NSDecimalNumber decimalNumberWithString:@"36.76662445068359375000"]; id xx4 = [NSDecimalNumber decimalNumberWithMantissa:3676662445068359375L exponent:-17 isNegative:NO]; NSLog(@"raw doubleValue: %.20f, %.17f", dbl, dbl); NSLog(@"xx1 doubleValue: %.20f, description: %@", [xx1 doubleValue], xx1); NSLog(@"xx2 doubleValue: %.20f, description: %@", [xx2 doubleValue], xx2); NSLog(@"xx3 doubleValue: %.20f, description: %@", [xx3 doubleValue], xx3); NSLog(@"xx4 doubleValue: %.20f, description: %@", [xx4 doubleValue], xx4);
The output is:
raw doubleValue: 36.76662445068359375000 36.76662445068359375 xx1 doubleValue: 36.76662445068357953915, description: 36.76662445068359168 xx2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168 xx3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375 xx4 doubleValue: 36.76662445068357953915, description: 36.76662445068359375
So you can see that when using the numberWithDouble
convenience method on NSNumber
(that you shouldn't really use due to it returning the wrong pointer type) and even the initialiser initWithDouble
(that IMO "should" be OK to call) you get a NSDecimalNumber with an internal representation (as returned by calling description
on the object) that is not as accurate as the one you get when you invoke decimalNumberWithString:
or decimalNumberWithMantissa:exponent:isNegative:
.
Also, note that converting back to a double by calling doubleValue
on the NSDecimalNumber
instance is losing precision but is, interestingly, the same no matter what initialiser you call.
So in the end, I think it is recommend that you use one of the decimalNumberWith*
methods declared at the NSDecimalNumber
class level to create your NSDecimalNumber instances when dealing with high precision floating point numbers.
So how do you call these initialisers easily when you have a double, float or other NSNumber?
Two methods described here "work" but still have precision issues.
If you already have the number stored as an NSNumber then this should work:
id n = [NSNumber numberWithDouble:dbl]; id dn1 = [NSDecimalNumber decimalNumberWithDecimal:[n decimalValue]]; NSLog(@" n doubleValue: %.20f, description: %@", [n doubleValue], n); NSLog(@"dn1 doubleValue: %.20f, description: %@", [dn1 doubleValue], dn1);
But as you can see from the output below it lops off some of the less significant digits:
n doubleValue: 36.76662445068359375000, description: 36.76662445068359 dn1 doubleValue: 36.76662445068357953915, description: 36.76662445068359
If the number is a primitive (float or double) then this should work:
id dn2 = [NSDecimalNumber decimalNumberWithMantissa:(dbl * pow(10, 17)) exponent:-17 isNegative:(dbl < 0 ? YES : NO)]; NSLog(@"dn2 doubleValue: %.20f, description: %@", [dn2 doubleValue], dn2);
But you will get precision errors again. As you can see in the output below:
dn2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168
The reason I think for this precision loss is due to the involvement of the floating point multiply, because the following code works fine:
id dn3 = [NSDecimalNumber decimalNumberWithMantissa:3676662445068359375L exponent:-17 isNegative:NO]; NSLog(@"dn3 doubleValue: %.20f, description: %@", [dn3 doubleValue], dn3);
Output:
dn3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375
So the most consistently accurate conversion/initialisation from a double
or float
to NSDecimalNumber is using decimalNumberWithString:
. But, as Brad Larson has pointed out in his answer, this might be a little slow. His technique for conversion using NSScanner might be better if performance becomes an issue.
There are some problems that can arise if you use NSNumber's initializers when creating an NSDecimalNumber. See the example posted in this question for a great case of that. Also, I trust Bill Bumgarner's word on this from his response in the cocoa-dev mailing list. See also Ashley's answer here.
Because I'm paranoid about these conversion issues, I've used the following code in the past to create NSDecimal structs:
NSDecimal decimalValueForDouble(double doubleValue) { NSDecimal result; NSString *stringRepresentationOfDouble = [[NSString alloc] initWithFormat:@"%f", doubleValue]; NSScanner *theScanner = [[NSScanner alloc] initWithString:stringRepresentationOfDouble]; [stringRepresentationOfDouble release]; [theScanner scanDecimal:&result]; [theScanner release]; return result; }
The reason for the complex code here is that I've found NSScanner to be about 90% faster than initializing an NSDecimalNumber from a string. I use NSDecimal structs because they can yield significant performance advantages over their NSDecimalNumber brothers.
For a simpler but slightly slower NSDecimalNumber approach, you could use something like
NSDecimalNumber *decimalNumberForDouble(double doubleValue) { NSString *stringRepresentationOfDouble = [[NSString alloc] initWithFormat:@"%f", doubleValue]; NSDecimalNumber *stringProcessor = [[NSDecimalNumber alloc] initWithString:stringRepresentationOfDouble]; [stringRepresentationOfDouble release]; return [stringProcessor autorelease]; }
However, if you're working with NSDecimalNumbers, you'll want to do what you can to avoid having a value become cast to a floating point number at any point. Read back string values from your text fields and convert them directly to NSDecimalNumbers, do your math with NSDecimalNumbers, and write them out to disk as NSStrings or decimal values in Core Data. You start introducing floating point representation errors as soon as your values are cast to floating point numbers at any point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With