如何解决 Java 舍入双精度问题

发布于 2024-07-07 01:33:45 字数 486 浏览 9 评论 0原文

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

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

壹場煙雨 2024-07-14 01:33:45

要控制浮点运算的精度,您应该使用 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

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.

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);

This results in the following output.

877.85 = 1586.6 - 708.75
画中仙 2024-07-14 01:33:45

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

正如之前的海报所建议的,当您进行数值计算时,请使用 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 的值。

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

在我的系统上给出

0.58499999999999996447286321199499070644378662109375
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 a BigDecimal, you have a choice of using a new BigDecimal(double) constructor or the BigDecimal.valueOf(double) static factory method. Use the static factory method.

The double constructor converts the entire precision of the double to a BigDecimal while the static factory effectively converts it to a String, then converts that to a BigDecimal.

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.

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

on my system gives

0.58499999999999996447286321199499070644378662109375
0.585
美胚控场 2024-07-14 01:33:45

另一个例子:

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 至少保证了这方面行为的一致性。

Another example:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0

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.

扭转时空 2024-07-14 01:33:45

我将按如下方式修改上面的示例:

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);

我认为这些选项比使用双打更好。 在网络应用程序中,数字无论如何都是以字符串开始的。

I would modify the example above as follows:

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);

This way you avoid the pitfalls of using string to begin with.
Another alternative:

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);

I think these options are better than using doubles. In webapps numbers start out as strings anyways.

寒冷纷飞旳雪 2024-07-14 01:33:45

任何时候你用双精度数进行计算时,这种情况都可能发生。 这段代码会给你 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;

拒绝两难 2024-07-14 01:33:45

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

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.

喵星人汪星人 2024-07-14 01:33:45

这是一个有趣的问题。

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 重新缩放的最后一步并不总是必要的。 目前我不知道如何正确确定这一点,所以如果有人知道如何那么我会很高兴听到它。

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:

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

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.

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

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.

若水般的淡然安静女子 2024-07-14 01:33:45

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

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

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.

小霸王臭丫头 2024-07-14 01:33:45

到目前为止,用 Java 实现这一点的最优雅、最有效的方法是:

double newNum = Math.floor(num * 100 + 0.5) / 100;

So far the most elegant and most efficient way to do that in Java:

double newNum = Math.floor(num * 100 + 0.5) / 100;
奶气 2024-07-14 01:33:45

最好使用 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

Better yet use JScience as BigDecimal is fairly limited (e.g., no sqrt function)

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
是你 2024-07-14 01:33:45
double rounded = Math.rint(toround * 100) / 100;
double rounded = Math.rint(toround * 100) / 100;
°如果伤别离去 2024-07-14 01:33:45

尽管您不应该使用双精度数进行精确计算,但如果您无论如何都要对结果进行四舍五入,那么以下技巧对我很有帮助。

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

Although you should not use doubles for precise calculations the following trick helped me if you are rounding the results anyway.

public static int round(Double i) {
    return (int) Math.round(i + ((i > 0.0) ? 0.00000001 : -0.00000001));
}

Example:

    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);

Which prints:

0.014999999999999965
0.01
0.02

More info: http://en.wikipedia.org/wiki/Double_precision

等风来 2024-07-14 01:33:45

这很简单。

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

例如:

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

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

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

It's quite simple.

Use the %.2f operator for output. Problem solved!

For example:

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

The above code results in a print output of:
877.85

The %.2f operator defines that only TWO decimal places should be used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文