15

我有一些特定于语言环境的字符串(例如,0.01 或 0,01)。我想将此字符串转换为 NSDecimalNumber。从到目前为止我在互联网上看到的示例中,这是通过使用 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。

4

4 回答 4

18

多年后:

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

于 2011-05-20T18:34:45.927 回答
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
于 2008-11-26T14:26:25.577 回答
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 的我自己的日期解析例程,并且我花了一个月的时间来处理所有有趣的边缘情况,(比如只有瑞典在其长日期中包含星期几)。

于 2008-11-25T16:23:34.577 回答
0

另请参阅在 C++ 中存储货币值的最佳方式

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

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

于 2008-11-26T16:32:59.517 回答