我正在为我正在处理的应用程序计算得到一些奇怪的结果,我认为这里的某个人可能能够帮助弄清楚发生了什么。
此特定计算的要求表明计算应如下所示:
A和B已知
A * B = C
对于这个特定的计算
A = 0.0410
B = 123456789010
以下是我看到的结果:
计算器:
0.0410 * 123456789010 = 5061728349.41
爪哇:
B是双倍的:
0.0410f * 123456789010d = 5.061728489223363E9 = 5061728489.223363
B很长:
0.0410f * 123456789010l = 5.0617288E9
对于我来说,精度的损失并不重要(我只需要9位数的精度),而不是10s和1s点的差异。为什么使用double进行计算会给我"错误"结果?
顺便说一下,我尝试使用BigDecimal
进行计算,并得到与使用double时相同的结果。
答案 0 :(得分:6)
发生的各种类型转换由JLS #5.6.2指定。在你的情况下(摘录):
- 如果任一操作数的类型为double,则另一个操作数转换为double。
- 否则,如果任一操作数的类型为float,则另一个操作数转换为float。
在0.0410f * 123456789010d = 506172848.9223363
中,0.0410f
首先转换为双倍,不一定等于0.0410d
。实际上你可以尝试一下,看看那不是:
double d1 = 0.041d;
double d2 = 0.041f;
System.out.println(new BigDecimal(d1));
System.out.println(new BigDecimal(d2));
输出:
0.041000000000000001720845688168992637656629085540771484375
0.041000001132488250732421875
在下一个例子中:
0.0410f * 123456789010L = 506172832
将long转换为float,您可以使用此示例验证:
float f1 = 0.0410f;
float f2 = 123456789010L;
System.out.println(new BigDecimal(f1)); // 0.041000001132488250732421875
System.out.println(new BigDecimal(f2)); // 123456790528
System.out.println(new BigDecimal(0.0410f * 123456789010L)); // 5061728768
System.out.println(new BigDecimal(f1 * f2)); // 5061728768
对于浮动/双重操作的精度,请检查this question。
最后,如果你使用BigDecimal,你会得到正确答案:
BigDecimal a = new BigDecimal("0.041");
BigDecimal b = new BigDecimal("123456789010");
System.out.println(a.multiply(b)); // outputs 5061728349.410
答案 1 :(得分:2)
TLDR答案:浮动无法更准确地代表“正确”答案。请改用双。此外,乘法也将在没有明确演员的情况下完成。
A B C
float long float 5061728768.000000
double long double 5061728489.223363
问题是浮点数的精度远小于一倍,所以当乘以一个大数(例如你的10 ^ 10值)时,你会在乘法中失去这个精度。如果我们明确地将A转换为双倍乘法:
double C = ((double)A)*B; //=5061728489.223363
然后我们回到额外的精度。如果我们将双重答案转回浮点数:
float C = (float)((double)((double)A)*B); //=5061728256.000000
您会看到答案再次不同。使用乘法的结果类型,因此在此实例中double
,但强制转换为float
会降低精度。如果没有明确的情况加倍(double C=A*B
),则使用float
类型。对于两个强制转换,乘法都是双精度,并且在乘法后精度会丢失。
答案 2 :(得分:1)
第一个计算使用double(64位),第二个浮点数(32位)。你所看到的是“四舍五入的错误”。
在这两种情况下,它都是浮点计算,但在第二种情况下,不涉及“双”参数,因此它只使用32位算法。
如果二元运算符的至少一个操作数是浮点类型,则操作是浮点运算,即使另一个是整数运算。
如果数值运算符的至少一个操作数是double类型,则使用64位浮点运算执行运算,并且数值运算符的结果是double类型的值。如果另一个操作数不是double,则首先将其扩展(第5.1.5节)以通过数字提升键入double(第5.6节)。
否则,使用32位浮点运算执行运算,并且数值运算符的结果是float类型的值。 (如果另一个操作数不是浮点数,则首先将其扩展为通过数字提升键入float。)
答案 3 :(得分:1)
您的问题的答案可能在Java语言规范的Floating point operation section和this older post中。由于隐含的转换正在发生,您可能会遇到舍入错误。
适用于您的情况的报价是
第三次操作:
如果二元运算符的至少一个操作数是 浮点类型,那么操作是浮点运算, 即使另一个是不可或缺的。
第二次操作:
如果数值运算符的至少一个操作数是类型的 double,然后使用64位浮点执行操作 算术,并且数值运算符的结果是值 双打。如果另一个操作数不是double,则首先加宽 (§5.1.5)通过数字提升键入double(§5.6)。
第一次操作
否则,使用32位浮点执行操作 算术,并且数值运算符的结果是值 键入float。 (如果另一个操作数不是浮点数,则首先加宽 通过数字促销键入float。)
因此,你不应该担心,但如果有必要,你可以决定你想要的精度是多少,并使用适当的铸造。
答案 4 :(得分:1)
32位IEEE浮点数具有7位精度; 64位允许16.这就是你得到的。如果这些都不够,则必须使用BigDecimal。
在实现IEE标准的每种语言中都是如此,而不仅仅是Java。