如何解决 Java 舍入双精度问题
似乎减法引发了某种问题,并且结果值是错误的。
double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;
78.75 = 787.5 * 10.0/100d
double netToCompany = targetPremium.doubleValue() - tempCommission;
708.75 = 787.5 - 78.75
double dCommission = request.getPremium().doubleValue() - netToCompany;
877.8499999999999 = 1586.6 - 708.75
最终的预期值为 877.85。
应该怎样做才能确保计算正确?
Seems like the subtraction is triggering some kind of issue and the resulting value is wrong.
double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;
78.75 = 787.5 * 10.0/100d
double netToCompany = targetPremium.doubleValue() - tempCommission;
708.75 = 787.5 - 78.75
double dCommission = request.getPremium().doubleValue() - netToCompany;
877.8499999999999 = 1586.6 - 708.75
The resulting expected value would be 877.85.
What should be done to ensure the correct calculation?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
要控制浮点运算的精度,您应该使用 java .math.BigDecimal。 阅读 John Zukowski 的对 BigDecimal 的需求了解更多信息。
根据您的示例,最后一行将使用 BigDecimal 如下所示。
这会产生以下输出。
To control the precision of floating point arithmetic, you should use java.math.BigDecimal. Read The need for BigDecimal by John Zukowski for more information.
Given your example, the last line would be as following using BigDecimal.
This results in the following output.
正如前面的答案所述,这是进行浮点运算的结果。
正如之前的海报所建议的,当您进行数值计算时,请使用 java.math.BigDecimal。
但是,使用 BigDecimal 存在一个问题。 当您从 double 值转换为
BigDecimal
时,您可以选择使用新的BigDecimal(double)
构造函数或BigDecimal.valueOf(double)静态工厂方法。 使用静态工厂方法。
double 构造函数将
double
的整个精度转换为BigDecimal
,而静态工厂则有效地将其转换为String
,然后将其转换为BigDecimal
。当您遇到这些微妙的舍入错误时,这一点就变得相关了。 数字可能显示为 0.585,但其内部值为“0.58499999999999996447286321199499070644378662109375”。 如果您使用 BigDecimal 构造函数,您将得到不等于 0.585 的数字,而静态方法将为您提供等于 0.585 的值。
在我的系统上给出
As the previous answers stated, this is a consequence of doing floating point arithmetic.
As a previous poster suggested, When you are doing numeric calculations, use
java.math.BigDecimal
.However, there is a gotcha to using
BigDecimal
. When you are converting from the double value to aBigDecimal
, you have a choice of using a newBigDecimal(double)
constructor or theBigDecimal.valueOf(double)
static factory method. Use the static factory method.The double constructor converts the entire precision of the
double
to aBigDecimal
while the static factory effectively converts it to aString
, then converts that to aBigDecimal
.This becomes relevant when you are running into those subtle rounding errors. A number might display as .585, but internally its value is '0.58499999999999996447286321199499070644378662109375'. If you used the
BigDecimal
constructor, you would get the number that is NOT equal to 0.585, while the static method would give you a value equal to 0.585.on my system gives
另一个例子:
使用 BigDecimal 代替。
编辑:
另外,只是指出这不是“Java”舍入问题。 其他语言展览
类似(尽管不一定一致)的行为。 Java 至少保证了这方面行为的一致性。
Another example:
Use BigDecimal instead.
EDIT:
Also, just to point out this isn't a 'Java' rounding issue. Other languages exhibit
similar (though not necessarily consistent) behaviour. Java at least guarantees consistent behaviour in this regard.
我将按如下方式修改上面的示例:
这样您就可以避免使用字符串开始的陷阱。
另一种选择:
我认为这些选项比使用双打更好。 在网络应用程序中,数字无论如何都是以字符串开始的。
I would modify the example above as follows:
This way you avoid the pitfalls of using string to begin with.
Another alternative:
I think these options are better than using doubles. In webapps numbers start out as strings anyways.
任何时候你用双精度数进行计算时,这种情况都可能发生。 这段代码会给你 877.85:
双重答案 = Math.round(dCommission * 100000) / 100000.0;
Any time you do calculations with doubles, this can happen. This code would give you 877.85:
double answer = Math.round(dCommission * 100000) / 100000.0;
保存的是美分而不是美元,输出时只需将其格式转换为美元即可。 这样您就可以使用不受精度问题影响的整数。
Save the number of cents rather than dollars, and just do the format to dollars when you output it. That way you can use an integer which doesn't suffer from the precision issues.
这是一个有趣的问题。
Timons 回复背后的想法是您指定一个 epsilon ,它代表合法双精度数可以达到的最小精度。 如果您知道在您的应用程序中永远不需要低于 0.00000001 的精度,那么他的建议足以获得非常接近事实的更精确的结果。 在他们预先知道最大精度的应用程序中很有用(例如货币精度的金融等)
然而,尝试四舍五入的根本问题是,当您除以一个因子来重新调整它时,您实际上引入了另一种精度的可能性问题。 对双打的任何操作都会以不同的频率引入不精确问题。 特别是如果您尝试以非常有效的数字进行四舍五入(因此您的操作数<0),例如,如果您使用 Timons 代码运行以下命令:
将导致
1499999.9999999998
这里的目标是以 500000 为单位进行舍入(即我们需要 1500000)。事实上,完全确定已消除不精确性的唯一方法是通过 BigDecimal 进行缩放。 例如,
结合使用 epsilon 策略和 BigDecimal 策略将使您能够更好地控制精度。 epsilon 的想法让你非常接近,然后 BigDecimal 将消除随后重新缩放造成的任何不精确性。 尽管使用 BigDecimal 会降低应用程序的预期性能。
有人向我指出,在某些用例中,当您可以确定没有输入值导致最终除法可能重新引入错误时,使用 BigDecimal 重新缩放的最后一步并不总是必要的。 目前我不知道如何正确确定这一点,所以如果有人知道如何那么我会很高兴听到它。
This is a fun issue.
The idea behind Timons reply is you specify an epsilon which represents the smallest precision a legal double can be. If you know in your application that you will never need precision below 0.00000001 then what he suggests is sufficient to get a more precise result very close to the truth. Useful in applications where they know up front their maximum precision (for in instance finance for currency precisions, etc)
However the fundamental problem with trying to round it off is that when you divide by a factor to rescale it you actually introduce another possibility for precision problems. Any manipulation of doubles can introduce imprecision problems with varying frequency. Especially if you're trying to round at a very significant digit (so your operands are < 0) for instance if you run the following with Timons code:
Will result in
1499999.9999999998
where the goal here is to round at the units of 500000 (i.e we want 1500000)In fact the only way to be completely sure you've eliminated the imprecision is to go through a BigDecimal to scale off. e.g.
Using a mix of the epsilon strategy and the BigDecimal strategy will give you fine control over your precision. The idea being the epsilon gets you very close and then the BigDecimal will eliminate any imprecision caused by rescaling afterwards. Though using BigDecimal will reduce the expected performance of your application.
It has been pointed out to me that the final step of using BigDecimal to rescale it isn't always necessary for some uses cases when you can determine that there's no input value that the final division can reintroduce an error. Currently I don't know how to properly determine this so if anyone knows how then I'd be delighted to hear about it.
请参阅对此问题的回复。 本质上,您所看到的是使用浮点运算的自然结果。
如果您觉得这样做很舒服,您可以选择一些任意精度(输入的有效数字?)并将结果四舍五入到它。
See responses to this question. Essentially what you are seeing is a natural consequence of using floating point arithmetic.
You could pick some arbitrary precision (significant digits of your inputs?) and round your result to it, if you feel comfortable doing that.
到目前为止,用 Java 实现这一点的最优雅、最有效的方法是:
So far the most elegant and most efficient way to do that in Java:
最好使用 JScience 因为 BigDecimal 相当有限(例如,没有 sqrt 函数)
Better yet use JScience as BigDecimal is fairly limited (e.g., no sqrt function)
尽管您不应该使用双精度数进行精确计算,但如果您无论如何都要对结果进行四舍五入,那么以下技巧对我很有帮助。
示例:
打印:
更多信息:http://en.wikipedia.org/wiki/Double_ precision
Although you should not use doubles for precise calculations the following trick helped me if you are rounding the results anyway.
Example:
Which prints:
More info: http://en.wikipedia.org/wiki/Double_precision
这很简单。
使用 %.2f 运算符进行输出。 问题解决了!
例如:
上述代码的打印输出为:
877.85
%.2f 运算符定义只应使用两位小数。
It's quite simple.
Use the %.2f operator for output. Problem solved!
For example:
The above code results in a print output of:
877.85
The %.2f operator defines that only TWO decimal places should be used.