85

似乎减法正在触发某种问题,并且结果值是错误的。

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。

应该怎么做才能确保正确计算?

4

13 回答 13

105

要控制浮点运算的精度,您应该使用java.math.BigDecimal。阅读John Zukowski 对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
于 2008-10-07T17:09:27.547 回答
76

正如前面的答案所述,这是进行浮点运算的结果。

正如之前的海报所建议的那样,当您进行数字计算时,请使用java.math.BigDecimal.

但是,使用BigDecimal. 当您从 double 值转换为 aBigDecimal时,您可以选择使用新的BigDecimal(double)构造函数或BigDecimal.valueOf(double)静态工厂方法。使用静态工厂方法。

double 构造函数将整个精度转换double为 aBigDecimal而静态工厂有效地将其转换为 a String,然后将其转换为 a BigDecimal

当您遇到那些细微的舍入错误时,这变得相关。一个数字可能显示为 0.585,但在内部它的值为 '0.58499999999999996447286321199499070644378662109375'。如果你使用BigDecimal构造函数,你会得到不等于 0.585 的数字,而静态方法会给你一个等于 0.585 的值。

双值 = 0.585;
System.out.println(new BigDecimal(value));
System.out.println(BigDecimal.valueOf(value));

在我的系统上给出

0.58499999999999996447286321199499070644378662109375
0.585
于 2008-10-07T17:20:34.403 回答
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 至少保证了这方面的一致行为。

于 2008-10-07T17:14:13.120 回答
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 中,数字无论如何都是以字符串开头的。

于 2009-04-30T20:42:03.287 回答
7

每当您使用双打进行计算时,都可能发生这种情况。此代码将为您提供 877.85:

双重答案 = Math.round(dCommission * 100000) / 100000.0;

于 2008-10-07T17:04:26.933 回答
4

保存美分而不是美元,输出时只需将其格式化为美元。这样您就可以使用不受精度问题影响的整数。

于 2008-10-07T17:09:40.307 回答
3

请参阅对此问题的回复。本质上,您所看到的是使用浮点运算的自然结果。

如果您觉得这样做很舒服,您可以选择一些任意精度(输入的有效数字?)并将结果四舍五入。

于 2008-10-07T17:02:42.350 回答
3

这是一个有趣的问题。

Timons 回复背后的想法是您指定一个 epsilon,它代表合法双精度可以达到的最小精度。如果您在应用程序中知道您永远不需要低于 0.00000001 的精度,那么他的建议足以获得更接近事实的更精确结果。在他们预先知道其最大精度的应用程序中很有用(例如金融货币精度等)

然而,试图四舍五入的根本问题是,当你除以一个因子来重新调整它时,你实际上引入了另一种精度问题的可能性。任何对双精度数的操作都可能引入频率不同的不精确问题。特别是如果您尝试以非常有效的数字四舍五入(因此您的操作数 < 0),例如,如果您使用 Timons 代码运行以下代码:

System.out.println(round((1515476.0) * 0.00001) / 0.00001);

将导致1499999.9999999998这里的目标是以 500000 为单位进行舍入(即我们想要 1500000)

实际上,完全确定您已经消除了不精确性的唯一方法是通过 BigDecimal 来缩减。例如

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());

使用 epsilon 策略和 BigDecimal 策略的组合将使您能够很好地控制您的精度。epsilon 的想法让您非常接近,然后 BigDecimal 将消除由之后重新缩放引起的任何不精确性。尽管使用 BigDecimal 会降低应用程序的预期性能。

有人向我指出,当您可以确定最终除法没有输入值可以重新引入错误时,某些用例并不总是需要使用 BigDecimal 重新调整它的最后一步。目前我不知道如何正确确定这一点,所以如果有人知道如何确定,我会很高兴听到它。

于 2012-04-05T12:50:42.127 回答
2

到目前为止,在 Java 中最优雅和最有效的方法是:

double newNum = Math.floor(num * 100 + 0.5) / 100;
于 2009-06-19T14:45:33.057 回答
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
于 2013-04-27T04:53:20.177 回答
1
double rounded = Math.rint(toround * 100) / 100;
于 2009-10-27T07:45:24.767 回答
-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

于 2011-11-30T13:35:35.117 回答
-3

这很简单。

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

例如:

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

上述代码的打印输出为:877.85

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

于 2012-03-11T15:07:26.783 回答