views:

278

answers:

3

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

+1  A: 

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

jessecurry
Thanks, I was definitely considering this as an option! Fortunately I managed to solve the `NSDecimalNumber` solution, which was always my preference.
Dave Hunt
+1  A: 


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.

imnk
Cheers, this solved part of my problem. Very helpful!
Dave Hunt
A: 

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.

Dave Hunt