从特定于语言环境的字符串中获取NSDecimalNumber?
我有一些特定于语言环境的字符串(例如,0.01或0.01)。我想将此字符串转换为NSDecimalNumber。从examples I've seen thus far on the interwebs,这是通过使用一个NSNumberFormatter一拉完成:从特定于语言环境的字符串中获取NSDecimalNumber?
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。
根据Boaz Stuller的回答,我为这个问题记录了一个苹果的bug。在解决问题之前,以下是我决定采取的最佳方法。这些变通办法只依赖于将十进制数四舍五入到适当的精度,这是一种简单的方法,可以补充您现有的代码(而不是从格式化程序切换到扫描程序)。
通用数字
从本质上讲,我只是根据四舍五入的规则,对于我的情况是有意义的数量。所以,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
这似乎工作:
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的自己的日期解析例程,而且我花了一个月时间来处理所有有趣的边缘案例(例如瑞典如何只有一天的时间包含在它的长期日期中)。
参见Best way to store currency values in C++
处理货币的最佳方法是使用一个整数值的货币的最小单位,即美分美元/欧元等您将避免代码中与浮点相关的精度错误。
考虑到这一点,以解析包含货币值的字符串最好的办法是做手工(与一个可配置的小数点字符)。在小数点处拆分字符串,并将第一个和第二个部分解析为整数值。然后使用构建你的综合价值。
感谢您的回答,unwesen。你的意见是处理货币的一个很好的经验法则(我已经在这样做)。但是,不要像您所描述的那样手动解析输入货币字符串,请参阅我的答案中的代码示例,该代码示例更加优雅地处理此问题。 – shek 2008-11-26 17:39:04
年后:
+(NSDecimalNumber *)decimalNumberWithString:(NSString *)numericString
在NSDecimalNumber
。
只是一个侧面说明,你应该格式化器的数字指向NSDecimalNumber指针。即NSDecimalNumber * decimalNumber =(NSDecimalNumber *)[formatter numberFromString:s]; – 2012-06-21 14:40:01