似乎减法触发了某种问题,结果值是错误的。
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。
应该采取哪些措施来确保正确的计算?
答案 0 :(得分:92)
要控制浮点运算的精度,您应该使用java.math.BigDecimal。阅读John Zukowski的The need for BigDecimal了解更多信息。
根据您的示例,最后一行将使用BigDecimal。
import java.math.BigDecimal;
BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);
这会产生以下输出。
877.85 = 1586.6 - 708.75
答案 1 :(得分:64)
如前所述,这是进行浮点运算的结果。
如上一张海报所示,当您进行数值计算时,请使用java.math.BigDecimal
。
但是,使用BigDecimal
还有一个问题。当您从double值转换为BigDecimal
时,您可以选择使用新的BigDecimal(double)
构造函数或BigDecimal.valueOf(double)
静态工厂方法。使用静态工厂方法。
双构造函数将double
的整个精度转换为BigDecimal
,而静态工厂有效地将其转换为String
,然后将其转换为BigDecimal
。
当您遇到那些微妙的舍入错误时,这就变得相关了。数字可能显示为.585,但在内部其值为“0.58499999999999996447286321199499070644378662109375”。如果使用BigDecimal
构造函数,则会得到不等于0.585的数字,而静态方法会给出等于0.585的值。
double value = 0.585; System.out.println(new BigDecimal(value)); System.out.println(BigDecimal.valueOf(value));
在我的系统上给出了
0.58499999999999996447286321199499070644378662109375 0.585
答案 2 :(得分:10)
另一个例子:
double d = 0;
for (int i = 1; i <= 10; i++) {
d += 0.1;
}
System.out.println(d); // prints 0.9999999999999999 not 1.0
改为使用BigDecimal。
编辑:
另外,只是指出这不是'Java'舍入问题。其他语言展出 类似(但不一定一致)的行为。 Java至少保证了这方面的一致行为。
答案 3 :(得分:8)
我会修改上面的例子如下:
import java.math.BigDecimal;
BigDecimal premium = new BigDecimal("1586.6");
BigDecimal netToCompany = new BigDecimal("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);
这样可以避免使用字符串开头的陷阱。 另一种选择:
import java.math.BigDecimal;
BigDecimal premium = BigDecimal.valueOf(158660, 2);
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);
我认为这些选项比使用双打更好。在webapps中,数字始终以字符串开头。
答案 4 :(得分:7)
任何时候你用双打进行计算,都会发生这种情况。这段代码会给你877.85:
double answer = Math.round(dCommission * 100000)/ 100000.0;
答案 5 :(得分:4)
保存美分而不是美元,并在输出时将格式设为美元。这样你就可以使用一个不受精度问题影响的整数。
答案 6 :(得分:3)
查看对this question的回复。基本上你所看到的是使用浮点运算的自然结果。
你可以选择一些任意精度(输入的有效位数?)并将结果四舍五入到你的位置,如果你觉得这样做很舒服的话。
答案 7 :(得分:3)
这是一个有趣的问题。
Timons回复背后的想法是你指定一个epsilon,它表示法律双重的最小精度。如果你在你的应用程序中知道你永远不会需要精度低于0.00000001,那么他所建议的就足以得到一个非常接近事实的更精确的结果。在他们预先知道其最大精度的应用程序中非常有用(例如,用于货币精确度的金融等)
然而,试图将其四舍五入的根本问题在于,当您除以一个因子来重新缩放它时,实际上会为精度问题引入另一种可能性。任何对双打的操纵都会引入不同频率的不精确问题。特别是如果你试图以非常重要的数字进行舍入(所以你的操作数是&lt; 0),例如如果你用Timons代码运行以下代码:
System.out.println(round((1515476.0) * 0.00001) / 0.00001);
将导致1499999.9999999998
这里的目标是以500000为单位进行舍入(即我们想要1500000)
事实上,完全确定你已经消除了不精确性的唯一方法是通过BigDecimal来缩小规模。 e.g。
System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());
使用epsilon策略和BigDecimal策略的混合将使您可以精确控制精度。作为epsilon的想法让你非常接近,然后BigDecimal将消除之后重新缩放造成的任何不精确。虽然使用BigDecimal会降低应用程序的预期性能。
有人向我指出,使用BigDecimal重新缩放它的最后一步并不总是需要一些用例,因为你可以确定没有输入值,最终的分区可以重新引入错误。目前我不知道如何正确地确定这一点,所以如果有人知道我怎么会很高兴听到它。
答案 8 :(得分:2)
到目前为止,在Java中这是最优雅,最有效的方法:
double newNum = Math.floor(num * 100 + 0.5) / 100;
答案 9 :(得分:2)
最好使用JScience,因为BigDecimal相当有限(例如,没有sqrt函数)
double dCommission = 1586.6 - 708.75;
System.out.println(dCommission);
> 877.8499999999999
Real dCommissionR = Real.valueOf(1586.6 - 708.75);
System.out.println(dCommissionR);
> 877.850000000000
答案 10 :(得分:0)
double rounded = Math.rint(toround * 100) / 100;
答案 11 :(得分:-1)
虽然您不应该使用双精度进行精确计算,但如果您要对结果进行四舍五入,则以下技巧对我有帮助。
public static int round(Double i) {
return (int) Math.round(i + ((i > 0.0) ? 0.00000001 : -0.00000001));
}
示例:
Double foo = 0.0;
for (int i = 1; i <= 150; i++) {
foo += 0.00010;
}
System.out.println(foo);
System.out.println(Math.round(foo * 100.0) / 100.0);
System.out.println(round(foo*100.0) / 100.0);
打印哪些:
0.014999999999999965
0.01
0.02
答案 12 :(得分:-3)
这很简单。
使用%.2f运算符进行输出。问题解决了!
例如:
int a = 877.8499999999999;
System.out.printf("Formatted Output is: %.2f", a);
以上代码产生的打印输出为: 877.85
%.2f运算符定义只应使用两位小数。