Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to instantiate an NSDecimalNumber from float or double

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...

like image 837
Paul Avatar asked Mar 14 '11 21:03

Paul


People also ask

Which is more accurate float or double float?

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.

Why do we use float instead of double in C++?

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.

How to print a float value as a double in printf?

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”.

How to instantiate an object that has dynamic storage duration?

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.


2 Answers

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.

like image 100
orj Avatar answered Sep 21 '22 13:09

orj


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.

like image 21
Brad Larson Avatar answered Sep 19 '22 13:09

Brad Larson