我应该使用 NSDecimalNumber 来处理金钱吗?
当我开始编写我的第一个应用程序时,我毫不犹豫地使用 NSNumber 来表示货币值。 然后我想也许 c 类型足以处理我的值。 然而,iPhone SDK 论坛建议我使用 NSDecimalNumber,因为它具有出色的舍入功能。
我不是一个数学家,我认为尾数/指数范式可能有点过分了; 尽管如此,通过谷歌搜索,我意识到大多数关于可可中货币/货币的讨论都提到了 NSDecimalNumber。
请注意,我正在开发的应用程序将国际化,因此以美分计算金额的选项实际上并不可行,因为货币结构很大程度上取决于所使用的区域设置。
我 90% 确定我需要使用 NSDecimalNumber,但由于我在网络上没有找到明确的答案(例如:“如果您处理金钱,请使用 NSDecimalNumber!”)我想我应该在这里问。 也许答案对大多数人来说都是显而易见的,但我想在开始对我的应用程序进行大规模重构之前确定一下。
说服我 :)
As I started coding my first app I used NSNumber for money values without thinking twice. Then I thought that maybe c types were enough to deal with my values. Yet, I was advised in the iPhone SDK forum to use NSDecimalNumber, because of its excellent rounding capabilities.
Not being a mathematician by temperament, I thought that the mantissa/exponent paradigm might be overkill; still, googlin' around, I realised that most talks about money/currency in cocoa were referred to NSDecimalNumber.
Notice that the app I am working on is going to be internationalised, so the option of counting the amount in cents is not really viable, for the monetary structure depends greatly on the locale used.
I am 90% sure that I need to go with NSDecimalNumber, but since I found no unambiguous answer on the web (something like: "if you deal with money, use NSDecimalNumber!") I thought I'd ask here. Maybe the answer is obvious to most, but I want to be sure before starting a massive re-factoring of my app.
Convince me :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
Marcus Zarra 对此有非常明确的立场:“如果您要处理货币,那么您应该使用 NSDecimalNumber。” 他的文章激励我研究 NSDecimalNumber,它给我留下了深刻的印象。 处理以 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 个分区,iPhone 上每秒 281,555 个添加,每秒 12,027 个分区)。
作为额外的好处,NSDecimalNumber 的 descriptionWithLocale: 方法提供了一个带有数字本地化版本的字符串,包括正确的小数分隔符。 对于它的 initWithString:locale: 方法也是如此。
Marcus Zarra has a pretty clear stance on this: "If you are dealing with currency at all, then you should be using NSDecimalNumber." His article inspired me to look into NSDecimalNumber, and I've been very impressed with it. IEEE floating point errors when dealing with base-10 math have been irritating me for a while (1 * (0.5 - 0.4 - 0.1) = -0.00000000000000002776) and NSDecimalNumber does away with them.
NSDecimalNumber doesn't just add another few digits of binary floating point precision, it actually does base-10 math. This gets rid of the errors like the one shown in the example above.
Now, I'm writing a symbolic math application, so my desire for 30+ decimal digit precision and no weird floating point errors might be an exception, but I think it's worth looking at. The operations are a little more awkward than simple var = 1 + 2 style math, but they're still manageable. If you're worried about allocating all sorts of instances during your math operations, NSDecimal is the C struct equivalent of NSDecimalNumber and there are C functions for doing the exact same math operations with it. In my experience, these are plenty fast for all but the most demanding applications (3,344,593 additions/s, 254,017 divisions/s on a MacBook Air, 281,555 additions/s, 12,027 divisions/s on an iPhone).
As an added bonus, NSDecimalNumber's descriptionWithLocale: method provides a string with a localized version of the number, including the correct decimal separator. The same goes in reverse for its initWithString:locale: method.
是的。 必须使用
NSDecimalNumber,而
在 iOS 上处理货币时,
不是 double 或 float。 这是为什么??
因为我们不想得到像 $9.9999999998 这样的东西,而不是 $10
这是怎么发生的?
浮点数和双精度数是近似值。 它们总是带有舍入误差。 计算机用于存储小数的格式会导致此路由错误。
如果您需要更多详细信息,请阅读
http://floating-point-gui.de/
根据苹果文档,
NSDecimalNumber 是 NSNumber 的不可变子类,提供面向对象的包装器用于执行以 10 为基数的算术。 实例可以表示任何可以表示为尾数 x 10^指数的数字,其中尾数是最多 38 位的十进制整数,指数是从 –128 到 127 的整数。用于进行以 10 为基数的算术的包装器。
所以推荐使用NSDecimalNumber来处理货币。
Yes. You have to use
NSDecimalNumber and
not double or float when you deal with currency on iOS.
Why is that??
Because we don't want to get things like $9.9999999998 instead of $10
How that happens??
Floats and doubles are approximations. They always comes with a rounding error. The format computers use to store decimals cause this rouding error.
If you need more details read
http://floating-point-gui.de/
According to apple docs,
NSDecimalNumber is an immutable subclass of NSNumber, provides an object-oriented wrapper for doing base-10 arithmetic. An instance can represent any number that can be expressed as mantissa x 10^exponent where mantissa is a decimal integer up to 38 digits long, and exponent is an integer from –128 through 127.wrapper for doing base-10 arithmetic.
So NSDecimalNumber is recommonded for deal with currency.
(改编自我对其他答案的评论。)
是的,你应该。 仅当您不需要代表(例如,半美分)时,整数个便士才有效。 如果发生这种情况,您可以将其更改为计算半美分,但如果您随后需要表示四分之一美分或八分之一美分怎么办?
唯一正确的解决方案是 NSDecimalNumber (或类似的东西),它将问题推迟到 10^-128 美分(即
0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000001 美分)。
(另一种方法是任意精度算术,但这需要一个单独的库,例如 GNU MP Bignum 库。 GMP 属于 LGPL。我从来没有使用过这个库,也不知道它到底是如何工作的,所以我不能说它对你有多好。)
[编辑:显然,至少有一个人——Brad Larson。 -认为我在这个答案中的某个地方谈论二进制浮点。 我不是。]
(Adapted from my comment on the other answer.)
Yes, you should. An integral number of pennies works only as long as you don't need to represent, say, half a cent. If that happens, you could change it to count half-cents, but what if you then need to represent a quarter-cent, or an eighth of a cent?
The only proper solution is NSDecimalNumber (or something like it), which puts off the problem to 10^-128¢ (i.e.,
0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000001¢).
(Another way would be arbitrary-precision arithmetic, but that requires a separate library, such as the GNU MP Bignum library. GMP is under the LGPL. I've never used that library and don't know exactly how it works, so I couldn't say how well it would work for you.)
[Edit: Apparently, at least one person—Brad Larson—thinks I'm talking about binary floating-point somewhere in this answer. I'm not.]
我发现用一个整数来表示分的数量然后除以 100 来表示很方便。 回避了整个问题。
I've found it convenient to use an integer to represent the number of cents and then divide by 100 for presentation. Avoids the whole issue.
更好的问题是,什么时候不应该使用 NSDecimalNumber 来处理金钱。 这个问题的简短答案是,当您无法容忍 NSDecimalNumber 的性能开销并且您不关心小的舍入误差时,因为您从不处理超过几位数的精度。 更简短的答案是,在处理金钱时您应该始终使用 NSDecimalNumber 。
A better question is, when should you not use NSDecimalNumber to deal with money. The short answer to that question is, when you can't tolerate the performance overhead of NSDecimalNumber and you don't care about small rounding errors because you're never dealing with more than a few digits of precision. The even shorter answer is, you should always use NSDecimalNumber when dealing with money.
VISA、MasterCard 和其他卡在传递金额时使用整数值。 发送方和接收方根据货币指数正确解析金额(除或乘以 10^num,其中 num - 是货币指数)。 请注意,不同的货币有不同的指数。 通常它是 2(因此我们除以并乘以 100),但某些货币的指数 = 0(VND 等)或 = 3。
VISA, MasterCards and others are using integer values while passing amounts. It's up to sender and reciever to parse amouts correctly according to currency exponent (divide or multiply by 10^num, where num - is an exponent of the currency). Note that different currencies have different exponents. Usually it's 2 (hence we divide and multiply by 100), but some currencies have exponent = 0 (VND,etc), or = 3.