从特定于语言环境的字符串中获取NSDecimalNumber?

时间:2008-11-25 13:25:33

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

我有一些特定于语言环境的字符串(例如,0.01或0,01)。我想将此字符串转换为NSDecimalNumber。从examples I've seen thus far on the interwebs开始,这可以通过使用NSNumberFormatter a la:

来实现
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。

4 个答案:

答案 0 :(得分:18)

多年后:

+(NSDecimalNumber *)decimalNumberWithString:(NSString *)numericString中的

NSDecimalNumber

答案 1 :(得分:13)

根据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

答案 2 :(得分:3)

这似乎有效:

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位小数,所以在不到一万亿美元的情况下,它们的价格会低于一分钱。我不得不为一个项目编写我自己的基于NSDateFormatter的日期解析例程,我花了一个月的时间来处理所有有趣的边缘情况,(比如瑞典在其漫长的约会中包含了一周的日期)。

答案 3 :(得分:0)

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

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

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