为什么不使用Double或Float来表示货币?

我总是被告知永远不能用doublefloat类型代表金钱,这次我向你提出问题:为什么?

我确信有一个很好的理由,我根本不知道它是什么。


因为花车和双打不能准确地代表我们用于金钱的10倍数。 这个问题不仅适用于Java,它适用于使用基2浮点类型的任何编程语言。

在基数10中,可以将10.25写为1025 * 10-2(整数乘以10的幂)。 IEEE-754浮点数是不同的,但考虑它们的一个非常简单的方法是乘以2的幂。 例如,你可能会看到164 * 2-4(整数乘以2的幂),它也等于10.25。 这不是数字在内存中的表现方式,而是数学运算方式相同。

即使在基数10,这个表示法也不能准确地表示最简单的分数。 例如,你不能表示1/3:十进制表示正在重复(0.3333 ...),所以没有有限整数可以乘以10的幂乘以1/3。 你可以用一个3的长序列和一个小指数来解决,比如333333333 * 10-10,但它不准确:如果你乘以3,你就不会得到1。

但是,为了数钱的目的,至少对于那些价值在美元数量级以内的国家来说,通常你所需要的只是能够存储10-2的倍数,所以它并不重要三分之一不能表示。

浮动和双打的问题在于绝大多数类似于钱的数字没有一个整数乘以2的幂的精确表示。事实上,在0和1之间的唯一倍数为0.01(这在处理时很重要与钱,因为他们是整数美分),可以完全表示为一个IEEE-754二进制浮点数是0,0.25,0.5,0.75和1.其他所有其他关闭了少量。 作为0.333333例子的类比,如果将浮点值设为0.1,并将其乘以10,则不会得到1。

代表货币为doublefloat时可能看起来不错,因为软件会消除这些微小的错误,但是当您对不精确的数字执行更多的增加,减法,乘法和除法时,错误将会变得复杂,并且最终会得到值显然不准确。 这使浮动和双打不足以处理金钱,要求基数为10的倍数的完美准确性。

一种适用于任何语言的解决方案是使用整数来代替美分。 例如,1025将是10.25美元。 几种语言也有内置类型来处理金钱。 其中,Java有BigDecimal类,C#有decimal类型。


来自Bloch,J.,Effective Java,第二版,第48项:

floatdouble类型特别不适合进行货币计算,因为它不可能将0.1(或10的任何其他负数)表示为floatdouble

例如,假设你有1.03美元,你花费42c。 你剩下多少钱?

System.out.println(1.03 - .42);

打印出0.6100000000000001

解决这个问题的正确方法是使用BigDecimalintlong来进行货币计算。


这不是一个准确的问题,也不是一个精确的问题。 这是满足人类使用基数10计算而不是基数2的期望的问题。例如,使用双打进行财务计算不会产生在数学意义上“错误”的答案,但它可以产生的答案是而不是财务意义上的预期。

即使您在输出前的最后一刻将结果舍入,仍然可以偶尔使用与预期不符的双打结果。

使用计算器或手工计算结果,1.40 * 165 = 231。 然而,在我的编译器/操作系统环境中,内部使用双精度数据时,它被存储为接近230.99999的二进制数...所以如果截断数字,则会得到230而不是231.您可能会推断舍入而不是截断会已经给出了期望的结果231.这是事实,但舍入总是涉及截断。 无论您使用哪种四舍五入技术,仍然存在像这样的边界条件,当您期望它四舍五入时,这种边界条件会下降。 它们非常罕见,通常不会通过偶然测试或观察发现。 您可能需要编写一些代码来搜索说明结果不符合预期的示例。

假设你想将某物转到最近的一分钱。 所以你把你的最终结果乘以100,加0.5,截断,然后将结果除以100,以便回到便士。 如果您存储的内部号码是3.46499999 ....而不是3.465,那么当您将号码四舍五入到最接近的一分钱时,您将获得3.46而不是3.47。 但是你的基数10计算可能已经表明答案应该是3.465,显然应该达到3.47,而不是3.46。 当您使用双打进行财务计算时,这些事情偶尔会在现实生活中发生。 这很少见,所以它经常被忽视作为一个问题,但它发生。

如果使用base 10代替双打进行内部计算,则假设代码中没有其他错误,则答案始终与人们所期望的完全相同。

链接地址: http://www.djcxy.com/p/2459.html

上一篇: Why not use Double or Float to represent currency?

下一篇: What is the difference between g++ and gcc?