如何解决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。
应该做些什么来确保正确的计算?
为了控制浮点运算的精度,你应该使用java.math.BigDecimal 。 阅读John Zukowski对BigDecimal的需求以获取更多信息。
给你的例子,最后一行将如下使用BigDecimal。
import java.math.BigDecimal; BigDecimal premium = BigDecimal.valueOf(1586.6d); BigDecimal netToCompany = BigDecimal.valueOf(708.75d); BigDecimal commission = premium.subtract(netToCompany); System.out.println(commission + " = " + premium + " - " + netToCompany);
这导致以下输出。
877.85 = 1586.6 - 708.75
正如前面的答案所述,这是做浮点运算的结果。
正如之前的海报提示的那样,在进行数字计算时,使用java.math.BigDecimal
。
但是,使用BigDecimal
有一个问题。 当您从double值转换为BigDecimal
,您可以选择使用新的BigDecimal(double)
构造函数或BigDecimal.valueOf(double)
静态工厂方法。 使用静态工厂方法。
double构造函数将double的整个精度转换为BigDecimal
而静态工厂将其转换为String
,然后将其转换为BigDecimal
。
当你遇到那些细微的舍入错误时,这变得相关。 一个数字可能显示为.585,但在内部它的值是'0.58499999999999996447286321199499070644378662109375'。 如果你使用BigDecimal
构造函数,你会得到不等于0.585的数字,而静态方法会给你一个等于0.585的值。
双倍值= 0.585; System.out.println(new BigDecimal(value)); 的System.out.println(BigDecimal.valueOf(值));
在我的系统给出
0.58499999999999996447286321199499070644378662109375 0.585
另一个例子:
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至少保证了这方面的一致行为。
我会修改上面的例子,如下所示:
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中,数字始终是字符串。
任何时候你做双打计算,都可能发生。 这段代码会给你877.85:
double answer = Math.round(dCommission * 100000)/ 100000.0;
保存美分的数量而不是美元,只要输出格式为美元即可。 这样你就可以使用一个不受精度问题影响的整数。
看到对这个问题的回应。 基本上你所看到的是使用浮点运算的自然结果。
你可以选择一些任意的精度(你的输入的有效数字?),并将结果舍入到它,如果你觉得这样做很舒服。
这是一个有趣的问题。
Timons答复背后的想法是指定一个代表法定双精度的最小精度的epsilon。 如果你在你的应用程序中知道你永远不需要低于0.00000001的精度,那么他所建议的就足以得到一个非常接近事实的更精确的结果。 在他们知道最高精度的应用中是有用的(例如用于货币精度的财务等)
然而,试图把它舍弃的根本问题是,当你除以一个因素来重新调整它,你实际上引入了另一个精度问题的可能性。 对双打的任何操纵都会引起频率不一的不精确问题。 特别是如果你试图在非常有意义的数字上进行舍入(所以你的操作数<0),例如,如果你用Timons码运行以下代码:
System.out.println(round((1515476.0) * 0.00001) / 0.00001);
将导致1499999.9999999998
这里的目标是在500000单位(即我们想要150万)
事实上,要完全确定你已经消除了不准确的唯一方法就是通过一个BigDecimal来缩小比例。 例如
System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());
使用epsilon策略和BigDecimal策略的组合可以很好地控制您的精度。 作为epsilon的想法让你非常接近,然后BigDecimal将消除之后重新缩放造成的任何不精确性。 尽管使用BigDecimal会降低应用程序的预期性能。
我已经指出,使用BigDecimal重新调整它的最后一步并不总是需要一些用例,当你可以确定没有输入值时,最终的分割可以重新引入一个错误。 目前我不知道该如何正确判断,所以如果有人知道我会很高兴听到这个消息。
更好的是使用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
到目前为止,在Java中这是最优雅和最有效的方法:
double newNum = Math.floor(num * 100 + 0.5) / 100;
double rounded = Math.rint(toround * 100) / 100;
虽然你不应该使用双打进行精确的计算,但是如果你总结了结果,下面的技巧帮助了我。
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
更多信息: http : //en.wikipedia.org/wiki/Double_precision
这很简单。
使用%.2f运算符输出。 问题解决了!
例如:
int a = 877.8499999999999; System.out.printf("Formatted Output is: %.2f", a);
上面的代码导致打印输出:877.85
%.2f运算符定义只能使用两个小数位。