【问题标题】:Obtaining an NSDecimalNumber from a locale specific string?从特定于语言环境的字符串中获取 NSDecimalNumber?
【发布时间】:2010-09-23 22:58:14
【问题描述】:

我有一些特定于语言环境的字符串(例如,0.01 或 0,01)。我想将此字符串转换为 NSDecimalNumber。在examples I've seen thus far on the interwebs 中,这是通过使用 NSNumberFormatter 来完成的:

NSString *s = @"0.07";

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[formatter setGeneratesDecimalNumbers:YES];
NSDecimalNumber *decimalNumber = [formatter numberFromString:s];

NSLog([decimalNumber stringValue]); // prints 0.07000000000000001

我正在使用 10.4 模式(除了根据文档推荐之外,它也是 iPhone 上唯一可用的模式)但向格式化程序指示我想要生成十进制数字。请注意,我已经简化了我的示例(我实际上是在处理货币字符串)。但是,我显然做错了,因为它返回了一个说明浮点数不精确的值。

将特定于语言环境的数字字符串转换为 NSDecimalNumber 的正确方法是什么?

编辑:请注意,我的示例是为了简单起见。我要问的问题也应该与您何时需要获取特定于语言环境的货币字符串并将其转换为 NSDecimalNumber。此外,这可以扩展为特定于语言环境的百分比字符串并将其转换为 NSDecimalNumber。

【问题讨论】:

    标签: iphone objective-c cocoa cocoa-touch internationalization


    【解决方案1】:

    根据 Boaz Stuller 的回答,我针对此问题向 Apple 记录了一个错误。在解决这个问题之前,以下是我认为最好的解决方法。这些变通方法仅依赖于将小数四舍五入到适当的精度,这是一种可以补充现有代码的简单方法(而不是从格式化程序切换到扫描程序)。

    通用编号

    基本上,我只是根据对我的情况有意义的规则对数字进行四舍五入。因此,YMMV 取决于您支持的精度。

    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [formatter setGeneratesDecimalNumbers:TRUE];
    
    NSString *s = @"0.07";
    
    // Create your desired rounding behavior that is appropriate for your situation
    NSDecimalNumberHandler *roundingBehavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:2 raiseOnExactness:FALSE raiseOnOverflow:TRUE raiseOnUnderflow:TRUE raiseOnDivideByZero:TRUE]; 
    
    NSDecimalNumber *decimalNumber = [formatter numberFromString:s];
    NSDecimalNumber *roundedDecimalNumber = [decimalNumber decimalNumberByRoundingAccordingToBehavior:roundingBehavior];
    
    NSLog([decimalNumber stringValue]); // prints 0.07000000000000001
    NSLog([roundedDecimalNumber stringValue]); // prints 0.07
    

    货币

    处理货币(这是我试图解决的实际问题)只是处理一般数字的一个细微差别。关键是舍入行为的比例由区域设置货币使用的最大小数位数决定。

    NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
    [currencyFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [currencyFormatter setGeneratesDecimalNumbers:TRUE];
    [currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    
    // Here is the key: use the maximum fractional digits of the currency as the scale
    int currencyScale = [currencyFormatter maximumFractionDigits];
    
    NSDecimalNumberHandler *roundingBehavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:currencyScale raiseOnExactness:FALSE raiseOnOverflow:TRUE raiseOnUnderflow:TRUE raiseOnDivideByZero:TRUE]; 
    
    // image s is some locale specific currency string (eg, $0.07 or €0.07)
    NSDecimalNumber *decimalNumber = (NSDecimalNumber*)[currencyFormatter numberFromString:s];
    NSDecimalNumber *roundedDecimalNumber = [decimalNumber decimalNumberByRoundingAccordingToBehavior:roundingBehavior];
    
    NSLog([decimalNumber stringValue]); // prints 0.07000000000000001
    NSLog([roundedDecimalNumber stringValue]); // prints 0.07
    

    【讨论】:

    • 附带说明,您应该将格式化程序的数字转换为指向 NSDecimalNumber 的指针。 IE。 NSDecimalNumber *decimalNumber = (NSDecimalNumber *) [formatter numberFromString:s];
    【解决方案2】:

    几年后:

    +(NSDecimalNumber *)decimalNumberWithString:(NSString *)numericStringNSDecimalNumber

    【讨论】:

    • 请注意某些语言环境中不同的小数分隔符。例如,在法语中,我们使用逗号“,”来分隔小数。在这种情况下,此行将无法正确转换。 [NSDecimalNumber decimalNumberWithString:string locale:[NSLocale currentLocale]] 工作正常,但是 :)
    • 这似乎在 2020 年不起作用:NSDecimalNumber(string: "9,277.235") --> 9277.235000000001
    【解决方案3】:

    另见Best way to store currency values in C++

    处理货币的最佳方法是对货币的最小单位使用整数值,即美分代表美元/欧元等。您将避免代码中出现任何与浮点相关的精度错误。

    考虑到这一点,解析包含货币值的字符串的最佳方法是手动进行(使用可配置的小数点字符)。在小数点处拆分字符串,并将第一部分和第二部分解析为整数值。然后用这些来构造你的组合值。

    【讨论】:

    • 感谢您的回答,unwesen。您的 cmets 是处理货币的一个很好的经验法则(我已经在这样做了)。但是,不要像您描述的那样手动解析输入货币字符串,而是在我的答案中查看我的代码示例,它可以更优雅地处理这个问题。
    • 具体来说,我的代码示例演示了跨任何区域设置货币字符串输入的处理。这比必须手动解析字符串并考虑分组、小数点分隔符、货币符号等各种特定于语言环境的规则要容易得多。
    【解决方案4】:

    这似乎有效:

    NSString *s = @"0.07";
    
    NSScanner* scanner = [NSScanner localizedScannerWithString:s];
    NSDecimal decimal;
    [scanner scanDecimal:&decimal];
    NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithDecimal:decimal];
    
    NSLog([decimalNumber stringValue]); // prints 0.07
    

    另外,提交一个关于此的错误。这绝对不是您在那里看到的正确行为。

    编辑:在 Apple 修复此问题(然后每个潜在用户都更新到固定的 OSX 版本)之前,您可能必须使用 NSScanner 滚动您自己的解析器或接受输入数字的“仅”双精度。除非您打算在此应用中使用五角大楼的预算,否则我建议您使用后者。实际上,双打精确到小数点后 14 位,因此在任何低于 1 万亿美元的情况下,它们的折扣都不到一分钱。我不得不为一个项目编写基于 NSDateFormatter 的我自己的日期解析例程,并且我花了一个月的时间处理所有有趣的边缘情况(比如只有瑞典在其长日期中包含星期几)。

    【讨论】:

    • 已记录错误 #6400434。感谢您的回复。正如我所提到的,我的例子被简化了(我实际上是在处理货币)。所以,我不确定这个答案是否适用于这种情况。
    • Boaz Stuller - 请参阅我的回答,该回答说明了一个非常简单地处理此问题的代码 sn-p,无需使用自定义解析器/NSScanner ......并且远低于五角大楼的预算。 ;)
    猜你喜欢
    • 2011-09-25
    • 2013-09-21
    • 1970-01-01
    • 2012-03-17
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多