4

我正在为我正在处理的应用程序进行计算时得到一些奇怪的结果,我认为这里的某个人可能能够帮助弄清楚发生了什么。

此特定计算的要求表明计算应如下所示:

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 

与 10 和 1 点的差异相比,精度损失对我来说并不重要(无论如何我只需要 9 位精度)。为什么使用 double 进行计算会给我“错误”的结果?

顺便说一句,我尝试使用BigDecimal并得到与使用双精度相同的结果。

4

5 回答 5

6

发生的各种类型转换由JLS #5.6.2指定。在您的情况下(摘录):

  • 如果任一操作数是 double 类型,则另一个操作数将转换为 double。
  • 否则,如果任一操作数的类型为浮点型,则另一个将转换为浮点型。

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.0410000000000000001720845688168992637656629085540771484375
0.041000001132488250732421875

在您的下一个示例中:

0.0410f * 123456789010L = 506172832

long 转换为浮点数,您可以通过以下示例进行验证:

    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

至于一般浮点/双精度操作的精度,请查看这个问题

最后,如果你使用 BigDecimal,你会得到正确的答案:

    BigDecimal a = new BigDecimal("0.041");
    BigDecimal b = new BigDecimal("123456789010");
    System.out.println(a.multiply(b)); // outputs 5061728349.410
于 2012-06-06T12:22:20.667 回答
2

TLDR 答案:浮点数不能更准确地表示“正确”答案。改用双精度。此外,在没有显式转换的情况下,乘法也会不精确地完成。

我使用http://www.ideone.com得到的答案

  A      B     C      
float  long  float  5061728768.000000
double long  double 5061728489.223363

问题是浮点数的精度远小于双精度数,因此当乘以一个大数(例如,您的 10^10 值)时,您会在乘法中失去这种精度。如果我们将 A 显式转换为 double 以进行乘法运算:

double C = ((double)A)*B; //=5061728489.223363

然后我们得到额外的精度。如果我们将双重答案转换为浮点数:

float C = (float)((double)((double)A)*B); //=5061728256.000000 

你看到答案又不一样了。使用了乘法的结果类型,因此在这种情况下double,但转换回float降低精度。如果没有明确的 double ( double C=A*B) 大小写,float则使用该类型。使用这两种强制转换,乘法都是作为双精度进行的,并且在乘法之后精度会丢失。

于 2012-06-06T12:24:26.307 回答
1

第一个计算使用双精度(64 位),第二个计算使用浮点数(32 位)。您看到的是“舍入错误”。

在这两种情况下,它都是浮点计算,但在第二种情况下,不涉及“双”参数,因此它只使用 32 位算术。

引用Java 语言规范

如果二元运算符的至少一个操作数是浮点类型,则该运算是浮点运算,即使另一个是整数。

如果数值运算符的至少一个操作数是 double 类型,则使用 64 位浮点算术执行运算,数值运算符的结果是 double 类型的值。如果另一个操作数不是双精度数,则首先将其扩展(第 5.1.5 节)以通过数字提升(第 5.6 节)键入双精度数。

否则,使用 32 位浮点运算进行运算,数值运算符的结果是浮点类型的值。(如果另一个操作数不是浮点数,则首先通过数字提升将其扩展为浮点数。)

于 2012-06-06T12:20:04.367 回答
1

您的问题的答案可能在Java 语言规范的浮点运算部分和这篇较早的文章中。由于正在发生的隐式转换,您可能会遇到舍入错误。

适用于您情况的报价是

第三次操作:

如果二元运算符的至少一个操作数是浮点类型,则该运算是浮点运算,即使另一个是整数。

第二次操作:

如果数值运算符的至少一个操作数是 double 类型,则使用 64 位浮点算术执行运算,数值运算符的结果是 double 类型的值。如果另一个操作数不是双精度数,则首先将其扩展(第 5.1.5 节)以通过数字提升(第 5.6 节)键入双精度数。

第一次操作

否则,使用 32 位浮点运算进行运算,数值运算符的结果是浮点类型的值。(如果另一个操作数不是浮点数,则首先通过数字提升将其扩展为浮点数。)

因此,您不必担心,而是确定您想要的精度并在必要时使用适当的铸件。

于 2012-06-06T12:24:25.793 回答
1

32 位 IEEE 浮点数具有七位精度;64 位允许 16 个。这就是你所得到的。如果这些都不够,您必须使用 BigDecimal。

在实现 IEE 标准的每一种语言中都是如此,而不仅仅是 Java。

于 2012-06-06T12:27:32.723 回答