如何解决Java四舍五入的双重问题

如何解决Java四舍五入的双重问题,java,math,rounding,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().

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

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.84999999999=1586.6-708.75

由此产生的预期值为877.85

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

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


您可以选择一些任意精度(输入的有效数字?)并对结果进行四舍五入,如果您愿意的话

任何时候用双倍进行计算时,都可能发生这种情况。此代码将为您提供877.85:


双答案=数学四舍五入(dCommission*100000)/100000.0

要控制浮点运算的精度,应使用。更多信息,请阅读John Zukowski的文章

给出您的示例,最后一行使用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

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

另一个示例:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0
int a = 877.8499999999999;
System.out.printf("Formatted Output is: %.2f", a);
double d=0;

对于(inti=1;i,如前面的答案所述,这是进行浮点运算的结果

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

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

双构造函数将
double
的整个精度转换为
BigDecimal
,而静态工厂将其有效地转换为
字符串,然后将其转换为
BigDecimal

当您遇到这些细微的舍入错误时,这一点就变得非常重要。数字可能显示为.585,但其内部值为“0.5849999999964447286321199499070644378662109375”。如果您使用
bigdecim
构造函数,您将得到不等于0.585的数字,而静态方法将为您提供一个值等于0.585

double value = 0.585; System.out.println(new BigDecimal(value)); System.out.println(BigDecimal.valueOf(value)); 双值=0.585; System.out.println(新的BigDecimal(值)); System.out.println(BigDecimal.valueOf(value)); 在我的系统上

0.58499999999999996447286321199499070644378662109375 0.585 0.58499999999999996447286321199499070644378662109375 0.585
我将对上述示例进行如下修改:

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中,数字以字符串开头。

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

double newNum = Math.floor(num * 100 + 0.5) / 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);
更多信息:

这很简单

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

例如:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0
int a = 877.8499999999999;
System.out.printf("Formatted Output is: %.2f", a);
上述代码导致打印输出为: 877.85

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

这是一个有趣的问题

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

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

System.out.println(round((1515476.0) * 0.00001) / 0.00001);
将导致
1499999.99999999 8
这里的目标是以500000为单位进行四舍五入(即我们想要1500000)

事实上,要完全确定你已经消除了不精确性,唯一的方法就是通过一个大的小数点来缩小

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());
混合使用epsilon策略和BigDecimal策略将使您能够很好地控制精度。epsilon的概念让您非常接近,然后BigDecimal将消除以后重新缩放所导致的任何不精确性。尽管使用BigDecimal会降低应用程序的预期性能

有人向我指出,使用BigDecimal重新缩放的最后一步并不总是必要的,因为在某些用例中,当您可以确定没有输入值时,最终的除法可以重新引入错误。目前我不知道如何正确地确定这一点,所以如果有人知道如何进行,那么我很高兴听到这一点

作为BigDecimal使用的更好方法是相当有限的(例如,没有sqrt函数)


无法完全“避免”浮点算术错误。用于表示数字的位数始终是有限的。您所能做的就是使用精度更高的数据类型(位数)。这是真的。我将编辑我的答案,以更准确地反映BigDecimal的用法。我将添加一个注释,BigDecimal除法需要与+、-、*稍微不同,因为默认情况下,如果它无法返回精确值(例如1/3),它将抛出异常。在类似的情况下,我使用了:BigDecimal.valueOf(a)。除法(BigDecimal.valueOf(b),25,RoundingMode.HALF_UP)。doubleValue(),其中25表示精度的最大位数(大于双精度结果所需的位数)。更好的divi
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