views:

1411

answers:

3

I've read a lot about NSDecimal, NSNumber, NSNumberDecimal, CFNumber... and it begins to be a kind of jungle to me.

Basically, I'm trying to create a simple model class that will handle simple computations, like this one:

#import <Foundation/Foundation.h>


@interface Test : NSObject
{
    float rate;
    float amount;
    int duration;
}

- (float)capitalizedAmount;

@end

@implementation Test

- (float)capitalizedAmount {
    return (amount*pow((1.0+rate),duration));
}

@end

I want to access these methods and setters with their names as strings, since I plan to have a lot of other classes like this one, and I am only keeping a list of field to do key value coding.

// This is just the desired behavior
// This evidently won't work with the previous class definition
Test *obj = [[Test alloc] init];
[NSNumber numberWithInt:10]
...
float r;
r = [obj performSelector:NSSelectorFromString(@"capitalizedAmount")];

I understand that this is not possible, that performSelector: will return an object, and thus that capitalizedAmount should return an object. I've read things about NSInvocation and the relevant part in the Objective-C Faq on comp.lang.

I also understand that I should use NSDecimalNumber, but there is two things that I would like to know:

  1. Are the memory overhead and performance loss acceptable for a somewhat more complicated class (only financial computations of this kind, showed in an UITableView)? I do not have much background in C...
  2. Isn't it too fastidious and complicated to use functions like decimalNumberByAdding:? With Python it was easy to define __add__ to use operators with objects. Should I get float values from NSDecimalNumber, then do the computations and returns the result wrapped in an NSDecimalNumber? How would you deal with this problem?

I am looking for a simple and beautiful solution!

Just another question in the same area: is CFBoolean the object wrapper for BOOL on iPhone Core Foundation?

Thank you very much for your help!

+6  A: 

If you are dealing with financial computations, you really should use base-10 arithmetic to avoid the rounding errors that can occur with the standard base-2 floating point types. So it's either NSDecimal or NSDecimalNumber. And since you're writing object-oriented code, NSDecimalNumber is the right choice for you.

To answer your questions: only testing of your code can reveal whether the memory overhead and performance loss are acceptable to you. I haven't really worked much with NSDecimalNumber but I'd wager that Apple's implementation is quite efficient and will be more than adequate for most people's needs.

Unfortunately, you won't be able to avoid the likes of decimalNumberByAdding: since Objective-C does not support operator overloading like C++ does. I agree that it makes your code somewhat less elegant.

One comment on the code you posted: r = [obj performSelector:NSSelectorFromString(@"capitalizedAmount")]; is rather unelegant. Either

r = [obj performSelector:@selector(capitalizedAmount)];

or even the simple

r = [obj capitalizedAmount];

would be better unless you require the NSSelectorFromString syntax for some other reason.

Ole Begemann
Thank you for your answer, I effectively need to create selectors from string.Is the CFBoolean the right object for wrapping BOOL?
charlax
In Cocoa, you should use NSNumber to wrap a BOOL in an object. See `+[NSNumber numberWithBool:]`.
Ole Begemann
A: 

I want to access these methods and setters with their names as strings, since I plan to have a lot of other classes like this one, and I am only keeping a list of field to do key value coding.

r = [obj performSelector:NSSelectorFromString(@"capitalizedAmount")];

KVC is not simply sending messages to objects using strings. See the Key-Value Coding Programming Guide.

Should I get float values from NSDecimalNumber, then do the computations and returns the result wrapped in an NSDecimalNumber?

No. Conversion to binary floating-point (float/double) from decimal floating-point (NSDecimal/NSDecimalNumber) is lossy. Your result will be incorrect for some calculations if you do them that way.

Peter Hosey
+6  A: 

Ole is correct, in that you should be using NSDecimal or NSDecimalNumber to avoid floating point math errors when doing financial calculations. However, my suggestion would be to use NSDecimal and its C functions, rather than NSDecimalNumber. NSDecimal calculations can be much faster, and since they avoid creating a lot of autoreleased objects, much better on memory usage.

As an example, I benchmarked math operations for the two types on my MacBook Air:

NSDecimal

Additions per second: 3355476.75
Subtractions per second: 3866671.27
Multiplications per second: 3458770.51
Divisions per second: 276242.32

NSDecimalNumber

Additions per second: 676901.32
Subtractions per second: 671474.6
Multiplications per second: 720310.63
Divisions per second: 190249.33

Divisions were the only operation that didn't experience a roughly fivefold increase in performance when using NSDecimal vs. NSDecimalNumber. Similar performance improvements occur on the iPhone. This, along with the memory savings, were why we recently switched Core Plot over to using NSDecimal.

The only difficulty you'll run into is in getting values into and out of the NSDecimal types. Going directly to and from float and integer values might require using NSDecimalNumber as a bridge. Also, if you use Core Data, you'll be storing your values as NSDecimalNumbers, not NSDecimals.

Brad Larson
Thank you Brad for your benchmark, which I had already seen and found very interesting. I plan on using NSDecimalNumber as I found them easier to use, and I will use Core Data in the near future. By the way, I don't need as much speed as your graphical library!
charlax