为什么在金融计算中应该使用十进制浮点数而不是二进制浮点数,即使它存在舍入误差?

5

我目前正在使用C++开发与股票市场相关的项目,其中涉及到很多浮点数类型,如价格和指数。

我阅读了很多关于在货币相关算术中应该使用十进制浮点数的文章。 为什么不使用Double或Float来表示货币? .NET中decimal、float和double之间的区别?

据我所知,浮点数和十进制浮点数的区别在于指数部分解释的基数不同,浮点数使用2作为基数,而十进制浮点数使用10。使用十进制浮点数仍然会有舍入误差,你仍然无法表达1/3(如果我理解错了,请纠正我),当你将某人的账户余额乘以30%时,可能会出现舍入误差,在进行更多计算后,舍入误差可能会更加严重。除了更大的数字范围外,我在财务算术中为什么应该使用十进制浮点数呢?


1
因为你无法用二进制表示0.01。 - Mysticial
注意:在0.1、0.2、0.3、……、0.9中,只有0.5是一个精确的二进制浮点数,而所有数字都是精确的十进制浮点数。 - user2249683
1
你可以做的最好的事情就是使用纯整数算术:不要用美元计算,而是用美分(或十分之一美分,如果你的账户精度是这样的)。 - Deduplicator
可能也存在效率问题。大多数浮点值都以3个部分存储。这三个部分必须被提取、处理,然后重新组合。与整数相比,这是两个额外的操作。整数没有打包问题。 - Thomas Matthews
值得一提的是,对于小数货币值来说,取30%不会有任何舍入问题。货币价值不会深入到最低位,而且小数可以准确表示百分比。 - Rudy Velthuis
2个回答

10
根据您进行的财务交易,舍入误差可能是不可避免的。如果一个物品售价为1.50美元,加上7%的销售税,您将不会被收取1.605美元; 您实际支付的价格将是1.60美元或1.61美元。(美国货币单位理论上包括“mils”,即一千分之一美元,但最小面额硬币是0.01美元,几乎所有交易都会四舍五入到最近的一分钱)。
如果您只是进行简单的计算(仅通过加减数量和乘以整数来计算),则所有结果都将是整数分。如果使用二进制浮点数表示美元数量,大多数金额将无法表示;本应产生0.01美元的计算可能会产生0.01000000000000000020816681711721685132943093776702880859375美元。
您可以通过使用整数来表示美分数量(或者等效地,如果语言支持,则使用定点表示法)或使用十进制浮点数来避免这个问题,后者可以精确表示0.01美元。

但对于更复杂的操作,例如计算7%的销售税,将一笔款项分成3个相等的部分,或者特别是复利,除非使用GMP这样的任意精度包,否则仍然会有无法准确表示的结果。

据我了解,有法律法规规定如何解决舍入误差。如果您将7%的销售税应用于1.50美元,您不能在1.60美元和1.61美元之间进行任选;法律告诉您哪一个是合法正确的。

如果您正在编写供他人使用的财务软件,则需要确定法规的具体内容。一旦知道了这一点,就可以确定哪种表示(整数、定点、十进制浮点数或其他)可以最好地用于获得法律要求的结果。

(免责声明:我不知道这些法规实际上说了什么。)


1
我想象着一场数百万美元的诉讼,因为一个程序将发票错误地四舍五入到1.61美元而不是1.60美元。 - Mysticial
5
@Mysticial: 我想象着一个金额达数百万美元的诉讼案,因为一个程序将一张账单错误地从 $1.60 向上舍入到了 $1.61,而这种错误发生了 十亿次 - Keith Thompson

1

至少在美国,大多数金融类公司都需要使用十进制数学。自IBM 360时代以来,主机可以对可变长度的紧缩十进制字符串进行数学运算。通常使用某种形式的定点数,小数点后有一定数量的数字。像Cobol这样的高级语言支持紧缩(或未紧缩)的十进制数。在IBM主机的情况下,与Cobol代码配套的遗留汇编代码很多,部分原因是曾经通过汇编宏访问某些类型的数据库(现在称为HLASM-高级汇编语言)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接