views:

1216

answers:

2

Hello,

iPhone/Objective-C/Cocoa newbie here. Based on a number of posts on stackoverflow, I have cobbled together an IBAction that I'm using in a basic iPhone calculator app that I'm building. The IBAction works with the numeric keypad to allow entry of decimal numbers without having to enter a decimal point.

I am trying very hard to adhere to the "use NSDecimal when dealing with currency" adage although I am finding it difficult to do so like so many others who have posted questions. I am making steady progress, but have hit a wall that I'm sure will look trivial after I get my head around NSDecimal and Format Specifications.

Here is the IBAction I'm using (it is triggered by Editing Changed UITextField Event):

// called when user touches a key or button
- (IBAction)processKeystrokes:(id)sender
{
 static BOOL toggle = YES; // was this method triggered by the user?

 // the user touched the keypad
 if (toggle)
 {
  toggle = NO;

  // retrieve the strings in input fields
  NSString *currencyField1Text = currencyField1.text;
  NSString *currencyField2Text = currencyField2.text;

  if (sender == currencyField1) {
   currencyField1Text = [currencyField1Text stringByReplacingOccurrencesOfString:@"." withString:@""];
   float currency = [currencyFieldText floatValue]/100; 
   currencyField1.text = [@"" stringByAppendingFormat:@"%0.2f", currency];
  }
  else if (sender == currencyField2) {
   currencyField2Text = [currencyField2Text stringByReplacingOccurrencesOfString:@"." withString:@""];
   NSDecimalNumber *currency2 = [[NSDecimalNumber decimalNumberWithString:currencyField2Text] decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:@"100"]]; 
   currencyField2.text = [@"" stringByAppendingFormat:@"%@", currency2];
  }
  else  {
   NSLog(@"Some unexpected input");   
  }  
 }
 else 
 {
  toggle = YES;
 }

} // end method calculateResults

The currencyField1 code segment uses floats, the currencyField2 segment uses NSDecimal.

The currencyField1 segment works as desired: displays all numbers with two digits after the decimal point (even when the delete key is used to delete all entered digits); however it suffers from and illustrates perfectly the problem with using floats when dealing with large currency values: rounding errors show up when entered numbers exceed 8 digits.

The currencyField2 segment avoids rounding error problem by using NSDecimal instead of float; however it does not always display numbers with two digits after the decimal point -- this is shown when the delete key is used to delete all entered digits. I believe the problem is due to this line of code:

currencyField2.text = [@"" stringByAppendingFormat:@"%@", currency2];

This is the corollary to the following line that produces the desired format for floats:

currencyField1.text = [@"" stringByAppendingFormat:@"%0.2f", currency];

So, I think I need the equivalent of @"%0.2f" for formatting the display of a "0" value NSDecimalNumber. I have been at this for so many hours that I'm embarrassed, but I just can't figure it out.

Any help or pointers are appreciated.

EDIT: I incorporated the NSNumberFormatter object (similar to what Brad describes in his comment) which seems to have solved the problem. However, I would like some feedback on refactoring the code now that I have it working. Here's the revised code:

// called when user touches a key or button
- (IBAction)processKeystrokes:(id)sender
{
 static BOOL toggle = YES; // was this method triggered by the user?

 // the user touched the keypad
 if (toggle)
 {
  toggle = NO;

  // retrieve the strings in input fields
  NSString *currencyField1Text = currencyField1.text;
  NSString *currencyField2Text = currencyField2.text;

  // new code elements
  NSNumberFormatter * nf = [[[NSNumberFormatter alloc] init]autorelease];
  [nf setNumberStyle:NSNumberFormatterCurrencyStyle];
  [nf setCurrencySymbol:@""];
  [nf setCurrencyGroupingSeparator:@""];

  if (sender == currencyField1) {
   currencyField1Text = [currencyField1Text stringByReplacingOccurrencesOfString:@"." withString:@""];
   NSDecimalNumber *currency = [[NSDecimalNumber decimalNumberWithString:currencyField1Text] decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:@"100"]]; 
   currencyField1.text = [nf stringFromNumber:currency];  
  }
  else if (sender == currencyField2) {
   currencyField2Text = [currencyField2Text stringByReplacingOccurrencesOfString:@"." withString:@""];
   NSDecimalNumber *currency2 = [[NSDecimalNumber decimalNumberWithString:currencyField2Text] decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:@"100"]]; 
   currencyField2.text = [nf stringFromNumber:currency2];
  }
  else  {
   NSLog(@"Some unexpected input");   
  }  
 }
 else 
 {
  toggle = YES;
 }

} // end method calculateResults

It addresses my initial problem, but I would appreciate any advice on how to improve it. Thanks.

A: 

If you want to guarantee 2 digits after the decimal point for your text value, you could use an NSNumberFormatter like in the following code (drawn from the answer here):

NSNumberFormatter *decimalNumberFormatter = [[NSNumberFormatter alloc] init];
[decimalNumberFormatter setMinimumFractionDigits:2];
[decimalNumberFormatter setMaximumFractionDigits:2];
currencyField2.text = [decimalNumberFormatter stringFromNumber:currency2];
[decimalNumberFormatter release];

I believe this should preserve the precision of the NSDecimalNumber. Personally, I prefer to use the NSDecimal C struct for performance reasons, but that's a little harder to get values into and out of.

Brad Larson
Thanks Brad. Your suggestion worked, but the leading "0" would get lost if the user deleted all entered digits. That would result in a displayed value of ".00" where I wanted "0.00". I fixed that with "[nf setNumberStyle:NSNumberFormatterCurrencyStyle];" which brought along the decimal points and two trailing digits; but also the currency symbol ("$") and currency group separator (",") which I eliminated using "[nf setCurrencySymbol:@""];" and "[nf setCurrencyGroupingSeparator:@""];" respectively. Thanks for the pointer -- NSNumberFormatter solved the problem!
Dondi
A: 

Hello everyone, I want to ask and I think still on the same topic. I have trouble when I have currency value

999999999999999999.99

From that value, I want to display it as String. The problem is that value always rounded to

1000000000000000000.00

I'm not expect that value rounded. I want the original value displayed. Do you have any idea how to solve this problem? Based on Brad Larson's answer, I tried this:

NSMutableString *aString = [NSMutableString stringWithCapacity:30];     

NSNumberFormatter *aCurrency = [[NSNumberFormatter alloc]init];     
[aCurrency setMinimumFractionDigits:2];
[aCurrency setMaximumFractionDigits:2];
[aString appendString:[aCurrency stringFromNumber:productPrice]];
//productPrice is NSDecimalNumber which is have value 999999999999999999.99

cell.detailTextLabel.text = aString;
NSLog(@"the price = %@",cell.detailTextLabel.text);
//it prints 1000000000000000000.00
[aCurrency release];
inot