Java BigDecimal 精度问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9795364/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 07:43:41  来源:igfitidea点击:

Java BigDecimal precision problems

javabigdecimaldouble-precision

提问by Jefferson

I know the following behavior is an old problem, but still I don't understand.

我知道以下行为是一个老问题,但我仍然不明白。

System.out.println(0.1 + 0.1 + 0.1);    

Or even though I use BigDecimal

或者即使我使用 BigDecimal

System.out.println(new BigDecimal(0.1).doubleValue()
    + new BigDecimal(0.1).doubleValue()
    + new BigDecimal(0.1).doubleValue());

Why this result is: 0.30000000000000004instead of: 0.3?

为什么这个结果是:0.30000000000000004而不是:0.3

How can I solve this?

我该如何解决这个问题?

采纳答案by Louis Wasserman

What you actually want is

你真正想要的是

new BigDecimal("0.1")
 .add(new BigDecimal("0.1"))
 .add(new BigDecimal("0.1"));

The new BigDecimal(double)constructor gets all the imprecision of the double, so by the time you've said 0.1, you've already introduced the rounding error. Using the Stringconstructor avoids the rounding error associated with going via the double.

new BigDecimal(double)构造得到的所有的不精确性double,所以由你说的时候0.1,你已经引入了舍入误差。使用String构造函数可以避免与通过double.

回答by James Cronen

Try this:

尝试这个:

BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1));

EDIT: Actually, looking over the Javadoc, this will have the same problem as the original. The constructor BigDecimal(double)will make a BigDecimal corresponding to the exact floating-point representation of 0.1, which is not exactly equal to 0.1.

编辑:实际上,查看 Javadoc,这将与原始问题存在相同的问题。构造函数BigDecimal(double)将生成一个 BigDecimal 对应于 0.1 的精确浮点表示,它不完全等于 0.1。

This, however, gives the exact result, since integers CAN always be expressed exactly in floating-point representation:

然而,这给出了精确的结果,因为整数总是可以精确地以浮点表示形式表示:

BigDecimal one = new BigDecimal(1);
BigDecimal oneTenth = one.divide(new BigDecimal(10));

BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth);

回答by Jakub Zaverka

This is not a problem of Java, but rather a problem of computers generally. The core problem lies in the conversion from decimal format (human format) to binary format (computer format). Some numbers in decimal format are not representable in binary format without infinite repeating decimals.

这不是Java的问题,而是一般计算机的问题。核心问题在于十进制格式(人类格式)到二进制格式(计算机格式)的转换。一些十进制格式的数字在没有无限重复小数的情况下不能用二进制格式表示。

For example, 0.3 decimal is 0.01001100... binary But a computer has a limited "slots" (bits) to save a number, so it cannot save all the whole infinite representation. It saves only 0.01001100110011001100 (for example). But that number in decimal is no longer 0.3, but 0.30000000000000004 instead.

例如,0.3 十进制是 0.01001100... 二进制 但是一台计算机有一个有限的“槽”(位)来保存一个数字,所以它不能保存所有的整个无限表示。它只保存 0.01001100110011001100(例如)。但是这个十进制数字不再是 0.3,而是 0.30000000000000004。

回答by Voo

First never, never use the double constructor of BigDecimal. It may be the right thing in a few situations but mostly it isn't

首先never,永远不要使用 BigDecimal 的双重构造函数。在某些情况下它可能是正确的,但大多数情况下它不是

If you can control your input use the BigDecimal String constructor as was already proposed. That way you get exactly what you want. If you already have a double (can happen after all), don't use the double constructor but instead the static valueOfmethod. That has the nice advantage that we get the cannonical representation of the double which mitigates the problem at least.. and the result is usually much more intuitive.

如果您可以控制您的输入,请使用已经提出的 BigDecimal String 构造函数。这样你就可以得到你想要的东西。如果您已经有一个 double(毕竟可能发生),请不要使用 double 构造函数,而是使用静态valueOf方法。这有一个很好的优势,即我们得到了 double 的规范表示,这至少可以缓解问题......而且结果通常更直观。

回答by Peter Lawrey

The problem you have is that 0.1 is represented with a slightly higher number e.g.

您遇到的问题是 0.1 用稍高的数字表示,例如

System.out.println(new BigDecimal(0.1));

prints

印刷

0.1000000000000000055511151231257827021181583404541015625

The Double.toString() takes into account this representation error so you don't see it.

Double.toString() 考虑了此表示错误,因此您看不到它。

Similarly 0.3 is represented by a value slightly lower than it really is.

同样,0.3 由比实际值略低的值表示。

0.299999999999999988897769753748434595763683319091796875

If you multiply the represented value of 0.1 by 3 you don't get the represented value for 0.3, you instead get something a little higher

如果将 0.1 的表示值乘以 3,则不会得到 0.3 的表示值,而是会得到更高的值

0.3000000000000000166533453693773481063544750213623046875

This is not just a representation error but also a rounding error caused by the operations. This is more than the Double.toString() will correct and so you see the rounding error.

这不仅是表示错误,也是由操作引起的舍入错误。这不仅仅是 Double.toString() 会更正,因此您会看到舍入错误。

The moral of the story, if you use floator doublealso round the solution appropriately.

故事的寓意,如果您适当地使用floatdouble也完善了解决方案。

double d = 0.1 + 0.1 + 0.1;
System.out.println(d);
double d2 = (long)(d * 1e6 + 0.5) / 1e6; // round to 6 decimal places.
System.out.println(d2);

prints

印刷

0.30000000000000004
0.3