开发者

Using NSNumberFormatter to get a decimal value from an international currency string

It seems that the NSNumberFormatter can't parse Euro (and probably other) currency strings into a numerical type. Can someone please prove me wrong.

I'm attempting to use the following to get a numeric amount from a currency string:

NSNumberFormatter *currencyFormatter = [[[NSNumberFormatter alloc] init] autorelease];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSNumber *currencyNumber = [currencyFormatter numberFromString:currencyString];

This works fine for UK and US currency amounts. It even deals with $ and £ and thousands separators with no problems.

However, when I use it with euro currency amounts (with the Region Format set to France or Germany in the settings app) it returns an empty string. All of the following strings fail:

12,34 €
12,34
12.345,67 €
12.345,67

It's worth noting that these strings match exactly what comes out of the NSNumberFormatter's stringFromNumber method when using the corresponding locale.

Setting the Region Format to France in the settings app, then setting currencyNumber to 12.34 in the following code, results in currencyString being set to '12,34 €' :

NSNumberFormatter *currencyFormatter = [[[NSNumberFormatter alloc] init] autorelease];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSString *currencyString = [currencyFormatter stringFromNumber:currencyNumber];

It would obviously be fairly easy to hack around this problem specifically for the Euro but I'm hoping to sell this app in as开发者_开发百科 many countries as possible and I'm thinking that a similar situation is bound to occur with other locales.

Does anyone have an answer?

TIA, Duncan


This is a difficult one. Doing the following works without issues:

double moneyAmount = 1256.34;
NSLocale *french = [[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"]; 
NSNumberFormatter *currencyStyle = [[NSNumberFormatter alloc] init];

[currencyStyle setLocale:french];
[currencyStyle setNumberStyle:NSNumberFormatterCurrencyStyle];
NSNumber *amount = [NSNumber numberWithDouble:moneyAmount];
NSString *amountString =  [currencyStyle stringFromNumber:amount];

NSNumber *pAmount = [currencyStyle numberFromString:amountString]; // == 1256.34

However, the output of the -currencyGroupingSeparator method returns the string \u00a0, a non-breaking space, rather than the expected ,.

The formatter classes (as well as the regex classes, among others) on OS X / iOS make use of the ICU library, also used by other projects. I found at least two other bug reports with regards to this particular issue. It appears that this is expected behaviour, though. (I spent some time sifting through the ICU source to find where this is defined, but it is a labyrinth.)

My recommendation, for the time being, would be to pass -setLenient:YES to the currency formatter. Perhaps even file a bug report with Apple's radar.


Have you tried setting the region to France in settings and then attempting to get the numeric amount for the string? Or you could manually set the local of the number formatter with setLocale:. NSNumberFormatter uses whatever locale it has to figure out what the numbers is a string are. If you just need the user to be able to put in a localized currency string for their area, change the system to that locale and then test. If you need the user to be able to put in multiple formats then add some sort of selection system so you know what they are putting in.


I verified that (at least on iOS 4.3) using the [formatter setLenient:YES] flag worked for me using Euros. Without that flag set, the formatter does not return correct values for Euros.


Its working code in Swift..

    let formatter = NumberFormatter()
    formatter.usesGroupingSeparator = true
    formatter.locale = Locale(identifier: "de_DE")  
    formatter.numberStyle = NumberFormatter.Style.currency
    formatter.string(from: NSNumber(floatLiteral: InputAsDoubleValue))!

Identifiers:

  • for German - "de_DE"
  • for US - "en_US"
  • for France - "fr_FR"

The formatted value would also be returned with the respective currency symbol.


I have an ugly answer using NSScanner

NSArray * testStrings = [NSArray arrayWithObjects:@"12,34 €", @"12,34" ,@"12.345,67 €" ,@"12.345,67", nil];

    NSCharacterSet * charset = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];

    for(NSString * s in testStrings)
    {
        s = [s stringByReplacingOccurrencesOfString:@"." withString:@""];
        s = [s stringByReplacingOccurrencesOfString:@"," withString:@"."];
        NSScanner * scanner = [NSScanner scannerWithString:s];
        [scanner setCharactersToBeSkipped:charset];
        float f;
        [scanner scanFloat:&f];
        NSLog(@"float output is %.2f",f);
    }

output:
2011-05-05 12:56:25.460 ScannerTest[10882:903] float output is 12.34
2011-05-05 12:56:25.473 ScannerTest[10882:903] float output is 12.34
2011-05-05 12:56:25.474 ScannerTest[10882:903] float output is 12345.67
2011-05-05 12:56:25.475 ScannerTest[10882:903] float output is 12345.67


With the default settings (lenient = NO), then it will only convert strings to numbers if they are formatted exactly as the strings it produces when formatting numbers to strings.

So in case you want to use non-lenient conversions, you should be aware that in many locales (all?) this means that spaces in the input string must be non-breaking (\u00a0). To wit:

NSString *amount = @"10 000,00 €";

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterCurrencyStyle;

formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier: @"fr_FR"];

NSNumber *number = [currencyFormatter numberFromString: amount];

This will not produce the expected result, as the number variable will be nil. To get the desired result, use non-breaking spaces in both places of the string (as a thousands separator and a currency symbol separator):

amount = @"10\u00a0000,00\u00a0€";

Then the conversion will happen as expected.

Of course, if you wish to use lenient conversions, then that is the easiest way to go, and it will probably be the best option to handle human input.

On the other hand, when handling machine-generated data, a lenient interpretation of the strings may not be desirable as you will lose the ability to check the correctness of the input data.


The way to do this in Swift is:

   var locale = NSLocale.currentLocale()
    var formatter = NSNumberFormatter()
    formatter.locale = NSLocale(localeIdentifier: "en_US")
    formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle

    var moneyDouble = formatter.numberFromString(textField.text)?.doubleValue

Assuming the textField.text is the number being entered.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜