Dave Hunt
Dave Hunt

Reputation: 8223

How to perform an accurate currency calculation in an iPhone app

I've written an iPhone App that calculates a total cost by adding/subtracting units of predetermined cost. This was working fine until I realised that the float values I was using caused the total to be inaccurate so I started to look into using NSDecimalNumber. I'm now rather confused and have an 'out of scope' error. Relevant sections of my (simplified) code are below. How can I get this to work as expected?

MyViewController.h

#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController {
    IBOutlet UILabel *totalPrice;
    NSDecimalNumber *total;
    NSDecimalNumber *item1Price;
}

@property (retain, nonatomic) NSDecimalNumber *total;
@property (retain, nonatomic) UILabel *totalPrice;

- (IBAction)addItem:(id)sender;
- (void)getDefaults;
- (void)setLabels;
@end

MyViewController.m

#import "MyViewController.h"

@implementation MyViewController

@synthesize total;
@synthesize totalPrice;

- (void)getDefaults {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"prices" ofType:@"plist"];
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
    total = [NSDecimalNumber decimalNumberWithString:@"0"];
    item1Price = (NSDecimalNumber *)[dict valueForKey:@"item1"];
}

- (void)setLabels {
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [numberFormatter setCurrencySymbol:@"£"];
    [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    totalPrice.text = [numberFormatter stringFromNumber:total];
}

- (void)viewDidLoad {
    [self getDefaults];
    [self setLabels];
}

- (IBAction)addItem:(id)sender {
    NSDecimalNumber *itemPrice;

    switch ([sender tag]) {
        case 1:
            itemPrice = item1Price;
            break;
    }

    total = [total decimalNumberByAdding:itemPrice];

    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [numberFormatter setCurrencySymbol:@"£"];
    [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];

    totalPrice.text = [numberFormatter stringFromNumber:total];
}

@end

I'm hoping that I'm on the right path...

Upvotes: 1

Views: 1075

Answers (3)

Dave Hunt
Dave Hunt

Reputation: 8223

There were two issues here. One was the scope error, which I resolved using retain as suggested by imnk. I'd already tried this out but my decimalNumberByAdding was giving inaccurate results.

It turns out that despite setting itemPrice1 up as a NSDecimalNumber the line:

item1Price = (NSDecimalNumber *)[dict valueForKey:@"item1"];

was changing it to a NSCFNumber. To solve this I changed this variable to an NSString and created a NSDecimalNumber from the string to use it in my method.

Upvotes: 0

imnk
imnk

Reputation: 4372


Your out of scope error could be coming from the fact that 'total' and 'itemPrice1' are defined as an instance variable and are used as such, i.e. you use them across the class. The getDefaults method sets their values, which are then used in other methods. However, by initialising these variable as:

 total = [NSDecimalNumber decimalNumberWithString:@"0"];
item1Price = (NSDecimalNumber *)[dict valueForKey:@"item1"];

...they autorelease at the end of that method (in that scope). You have two options to initialise:

total = [[NSDecimalNumber decimalNumberWithString:@"0"] retain];
// OR
total = [[NSNumber alloc] initWithString:@"0"];

No matter what option you choose from the above two, you will need to release later (usually in the dealloc method of the class).

Not sure what the problem was using NSNumber. You mentioned float not being accurate enough, in which case I would just use a double instead. You could do this:

double totalSetter = 0.0;
NSNumber *total = [[NSNumber alloc] initWithDouble:totalCaster];

and then retrieve using:

double totalGetter = [total doubleValue];

Now you should be able to use the NSNumberFormatter with the NSNumbers you've made.

Upvotes: 1

jessecurry
jessecurry

Reputation: 22116

You could use integers to do the calculation, just multiply out the fractional part and divide to get back to the correct value.

Otherwise you may want to try a double, that's what is actually used for most of the SDK data types that require precision(ie: CLLocationCoordinate, NSTimeInterval).

Upvotes: 1

Related Questions