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

谁说胖子不能爱 提交于 2019-12-02 20:33:44

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.

Shailesh Tyagi

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!