55

当我开始编写我的第一个应用程序时,我毫不犹豫地将 NSNumber 用于货币价值。然后我想也许 c 类型足以处理我的价值观。然而,iPhone SDK 论坛建议我使用 NSDecimalNumber,因为它具有出色的舍入功能。

不是气质上的数学家,我认为尾数/指数范式可能有点矫枉过正。尽管如此,谷歌搜索,我意识到大多数关于可可货币/货币的讨论都提到了 NSDecimalNumber。

请注意,我正在开发的应用程序将国际化,因此以美分计算金额的选项实际上并不可行,因为货币结构很大程度上取决于所使用的语言环境。

我 90% 确定我需要使用 NSDecimalNumber,但由于我在网络上没有找到明确的答案(例如:“如果你处理金钱,请使用 NSDecimalNumber!”)我想我会在这里问。也许答案对大多数人来说是显而易见的,但在开始对我的应用程序进行大规模重构之前,我想确定一下。

说服我 :)

4

6 回答 6

66

Marcus Zarra 对此有非常明确的立场:“如果你正在处理货币,那么你应该使用 NSDecimalNumber。” 他的文章启发我研究 NSDecimalNumber,我对它印象非常深刻。处理 base-10 数学时的 IEEE 浮点错误让我恼火了一段时间 (1 * (0.5 - 0.4 - 0.1) = -0.00000000000000002776) 并且 NSDecimalNumber 消除了它们。

NSDecimalNumber 不只是添加另外几位二进制浮点精度,它实际上做了以 10 为底的数学运算。这消除了上面示例中所示的错误。

现在,我正在编写一个符号数学应用程序,所以我希望 30+ 十进制数字精度和没有奇怪的浮点错误可能是一个例外,但我认为它值得一看。这些操作比简单的 var = 1 + 2 风格的数学有点尴尬,但它们仍然易于管理。如果您担心在数学运算期间分配各种实例,则 NSDecimal 是 NSDecimalNumber 的 C 结构等价物,并且有 C 函数可以使用它进行完全相同的数学运算。根据我的经验,除了要求最苛刻的应用程序(MacBook Air 上 3,344,593 个添加/秒、254,017 个分区/秒、281,555 个添加/秒、iPhone 上 12,027 个分区/秒)之外,这些速度都非常快。

作为一个额外的好处,NSDecimalNumber 的 descriptionWithLocale: 方法提供了一个带有数字本地化版本的字符串,包括正确的小数分隔符。它的 initWithString:locale: 方法也是如此。

于 2009-01-07T22:46:12.287 回答
11

是的。你必须使用

NSDecimalNumber

在 iOS 上处理货币时不要加倍浮动。

这是为什么??

因为我们不想得到像9.9999999998 美元而不是10美元这样的东西

这是怎么回事??

浮点数和双精度数是近似值。它们总是带有舍入误差。计算机用于存储小数的格式会导致此路由错误。如果您需要更多详细信息,请阅读

http://floating-point-gui.de/

根据苹果文档,

NSDecimalNumber 是 NSNumber 的不可变子类,为进行 base-10 算术提供了一个面向对象的包装器。实例可以表示可以表示为尾数 x 10^exponent 的任何数字,其中尾数是最多 38 位长的十进制整数,指数是从 –128 到 127.wrapper 的整数,用于进行以 10 为底的算术运算。

所以 NSDecimalNumber 被推荐用于处理货币。

于 2013-06-14T11:02:09.320 回答
5

(改编自我对另一个答案的评论。)

是的你应该。整数个便士仅在您不需要代表(例如,半美分)时才有效。如果发生这种情况,您可以将其更改为计算半美分,但如果您需要表示四分之一美分或八分之一美分怎么办?

唯一正确的解决方案是NSDecimalNumber(或类似的东西),这使关问题10 ^ -128¢(即,
0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000001¢)。

(另一种方法是任意精度算术,但这需要一个单独的库,例如GNU MP Bignum 库。GMP属于 LGPL。我从未使用过该库,也不知道它是如何工作的,所以我不能说它对你有多好。)

[编辑:显然,至少有一个人——布拉德·拉森——认为我在这个答案的某个地方谈论二进制浮点。我不是。]

于 2009-01-07T22:26:13.203 回答
4

我发现使用整数表示美分数然后除以 100 来表示很方便。回避整个问题。

于 2009-01-07T18:37:02.790 回答
4

一个更好的问题是,什么时候不应该使用NSDecimalNumber 来处理金钱。对这个问题的简短回答是,当您不能容忍 NSDecimalNumber 的性能开销并且您不关心小的舍入误差时,因为您处理的精度永远不会超过几位数。更简短的答案是,在处理金钱时,您应该始终使用 NSDecimalNumber。

于 2009-01-09T19:50:11.027 回答
2

VISA、万事达卡和其他卡在传递金额时使用整数值。根据货币指数(除以或乘以 10^num,其中 num - 是货币的指数)正确解析金额取决于发送方和接收方。请注意,不同的货币有不同的指数。通常它是 2(因此我们除以 100 并相乘),但有些货币的指数 = 0(VND 等),或 = 3。

于 2015-04-08T03:23:39.647 回答