如何解决Java Rounding Double问题

时间:2008-10-07 16:59:41

标签: java math rounding

似乎减法触发了某种问题,结果值是错误的。

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。

应该采取哪些措施来确保正确的计算?

13 个答案:

答案 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

更多信息:http://en.wikipedia.org/wiki/Double_precision

答案 12 :(得分:-3)

这很简单。

使用%.2f运算符进行输出。问题解决了!

例如:

int a = 877.8499999999999;
System.out.printf("Formatted Output is: %.2f", a);

以上代码产生的打印输出为: 877.85

%.2f运算符定义只应使用两位小数。