开发者

iPhone Currency input using NSDecimal instead of float

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 curren开发者_运维问答cy" 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.


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.


Can't you use the -setFormat: method (of NSNumberFormatter) instead for the NSNumberFormatter? Seems one should be able to configure it for your purposes and you wouldn't have to deal with weird "hacks" on a currency formatted string.

For more info see:

  • Apple's docs on -setFormat
  • Accepted format strings
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜