我总是被告知永远不要用双类型或浮点类型来表示货币,这一次我向你提出一个问题:为什么?

我相信有一个很好的理由,我只是不知道是什么。


当前回答

Float is binary form of Decimal with different design; they are two different things. There are little errors between two types when converted to each other. Also, float is designed to represent infinite large number of values for scientific. That means it is designed to lost precision to extreme small and extreme large number with that fixed number of bytes. Decimal can't represent infinite number of values, it bounds to just that number of decimal digits. So Float and Decimal are for different purpose.

有一些方法可以管理货币值的错误:

使用长整数,以分计算。 使用双精度,保持你的有效数字为15,这样小数可以精确模拟。在显示值之前舍入;做计算时经常四舍五入。 使用像Java BigDecimal这样的十进制库,这样就不需要使用double来模拟十进制。

附注:有趣的是,大多数品牌的手持科学计算器工作在十进制而不是浮点数。所以没有人抱怨浮点数转换错误。

其他回答

我对其中一些回答感到困扰。我认为双数和浮点数在财务计算中占有一席之地。当然,在使用整数类或BigDecimal类时,在加减非分数货币金额时,不会损失精度。但是,当执行更复杂的操作时,无论您如何存储这些数字,您经常会得到小数点后几位或许多位的结果。问题在于你如何呈现结果。

如果你的结果是在四舍五入和四舍五入之间的边缘,最后一分真的很重要,你可能应该告诉观众答案几乎在中间——通过显示更多的小数点后数位。

双精度浮点数的问题是,当它们被用来组合大数和小数时。在java中,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

结果

1.1875

The result of floating point number is not exact, which makes them unsuitable for any financial calculation which requires exact result and not approximation. float and double are designed for engineering and scientific calculation and many times doesn’t produce exact result also result of floating point calculation may vary from JVM to JVM. Look at below example of BigDecimal and double primitive which is used to represent money value, its quite clear that floating point calculation may not be exact and one should use BigDecimal for financial calculations.

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

输出:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

大多数回答都强调了为什么不应该使用替身来计算金钱和货币。我完全同意他们的观点。

但这并不是说,double永远不能用于这个目的。

我曾经参与过许多gc需求非常低的项目,BigDecimal对象是造成这种开销的一个重要因素。

正是由于缺乏对双重表示的理解,以及缺乏处理准确性和精确性的经验,才产生了这个明智的建议。

如果您能够处理项目的精度和准确性要求,则可以使其工作,这必须基于处理的双精度值的范围来完成。

你可以参考番石榴的FuzzyCompare方法来获得更多的信息。参数公差是关键。 我们为一个证券交易应用程序处理了这个问题,并对在不同范围内对不同数值使用什么公差做了详尽的研究。

此外,在某些情况下,您可能会试图使用Double包装器作为映射键,并将哈希映射作为实现。这是非常危险的,因为双重。等号和哈希码,例如值“0.5”和“0.6 - 0.1”将导致一个大混乱。

因为浮点数和双精度数不能准确地表示我们用来表示金钱的以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的倍数,所以1/3不能表示并没有什么关系。

The problem with floats and doubles is that the vast majority of money-like numbers don't have an exact representation as an integer times a power of 2. In fact, the only multiples of 0.01 between 0 and 1 (which are significant when dealing with money because they're integer cents) that can be represented exactly as an IEEE-754 binary floating-point number are 0, 0.25, 0.5, 0.75 and 1. All the others are off by a small amount. As an analogy to the 0.333333 example, if you take the floating-point value for 0.01 and you multiply it by 10, you won't get 0.1. Instead you will get something like 0.099999999786...

把钱表示成双位数或浮点数一开始可能看起来不错,因为软件会消除微小的错误,但当你对不精确的数字进行更多的加减乘除运算时,错误就会加剧,最终你会得到明显不准确的数值。这使得浮点数和双精度数不适用于处理货币,因为货币需要精确计算以10为底数的倍数。

一种适用于任何语言的解决方案是使用整数,并计算美分。例如,1025就是10.25美元。一些语言也有内置的类型来处理钱。其中,Java有BigDecimal类,Rust有rust_decimal板条箱,c#有decimal类型。

如果你的计算涉及到不同的步骤,任意的精度算法都不能100%覆盖你。

使用完美的结果表示(使用自定义Fraction数据类型,将除法操作批处理到最后一步)并且仅在最后一步转换为十进制的唯一可靠方法。

任意精度不会有帮助,因为总有可能有很多小数点后的数字,或者一些结果,如0.6666666……最后一个例子没有任意的表示法。所以每一步都会有小误差。

这些错误会累积起来,最终可能变得不再容易被忽视。这被称为错误传播。