【问题标题】:Why do these two multiplication operations give different results?为什么这两个乘法运算会给出不同的结果?
【发布时间】:2012-09-27 07:45:32
【问题描述】:

为什么我需要添加一个“L”字母才能获得正确的长值?另一个值是什么?

long oneYearWithL = 1000*60*60*24*365L;
long oneYearWithoutL = 1000*60*60*24*365;
System.out.println(oneYearWithL);//gives correct calculation result : 31536000000
System.out.println(oneYearWithoutL)//gives incorrect calculation result: 1471228928

【问题讨论】:

    标签: java int long-integer multiplication operation


    【解决方案1】:

    如果没有L,您的计算将作为 32 位值执行。如果将值表示为十六进制,则较小的值只是较大值的低 4 个字节。

    Java 默认为 32 位整数类型。对于 Long,L 是 64 位。通过将L 放在365 之后,您告诉编译器将365 视为long 值。当 32 位和 64 位值相乘时,编译器会将 32 位值向上转换为 64 位,以便计算表达式的中间结果保留完整的 64 位范围。

    请参阅Java Language Specification 中的原始类型和值

    乘法的行为在https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.17.1明确定义,如下:

    15.17.1 乘法运算符 *

    二进制* 运算符执行乘法,产生其操作数的乘积。如果操作数表达式没有副作用,则乘法是一种交换运算。当操作数都是相同类型时,整数乘法是关联的,而浮点乘法不是关联的。 如果整数乘法溢出,则结果是数学乘积的低位,以某种足够大的二进制补码格式表示。因此,如果发生溢出,则结果的符号可能与两个操作数值的数学乘积的符号不同。

    浮点乘法的结果受 IEEE 754 算术规则的约束:

    • 如果任一操作数为NaN,则结果为NaN
    • 如果结果不是NaN,则如果两个操作数的符号相同,则结果的符号为正,如果两个操作数的符号不同,则结果的符号为负。
    • 无穷大乘以零得到 NaN。
    • 将无穷大与有限值相乘会得到有符号无穷大。标志由上述规则确定。
    • 在其余情况下,既不涉及无穷大也不涉及 NaN,将计算精确的数学乘积。然后选择一个浮点值集:
      • 如果乘法表达式是 FP-strict (§15.4):
        • 如果乘法表达式的类型为float,则必须选择浮点值集。
        • 如果乘法表达式的类型为double,则必须选择双精度值集。
      • 如果乘法表达式不是 FP-strict:
        • 如果乘法表达式的类型是 float,则可以选择浮点值集或浮点扩展指数值集,具体取决于实现。
        • 如果乘法表达式的类型是 double,则可以选择双精度值集或双精度扩展指数值集,具体取决于实现。

    接下来,必须从选定的值集中选择一个值来表示产品。

    如果乘积的量级太大而无法表示,我们说操作溢出;结果是一个无穷大的适当符号。

    否则,将使用 IEEE 754 舍入到最近模式将乘积舍入到所选值集中的最接近值。 Java 编程语言需要支持 IEEE 754 (§4.2.4) 定义的逐渐下溢。

    尽管可能发生上溢、下溢或信息丢失,但乘法运算符* 的求值永远不会引发运行时异常。

    【讨论】:

      【解决方案2】:
      long oneYearWithL = 1000*60*60*24*365L;
      long oneYearWithoutL = 1000*60*60*24*365;
      

      你的第一个值实际上是一个长整数(因为365L 是一个long,而1000*60*60*24 是一个integer,所以multiplying 一个long 值和一个integer 值的结果是long 值。

      但是第二个值是一个整数(因为您只是将一个 integer 值与一个 integer 值相乘。所以结果将是一个 32-bit 整数。现在为该 multiplication 获得的结果超出了整数的实际范围。因此,在分配给变量之前,它会被截断以适应有效的整数范围。

      看看下面的打印语句:-

      System.out.println(1000*60*60*24*365L);
      System.out.println(1000*60*60*24*365);
      System.out.println(Integer.MAX_VALUE);
      

      当你运行上面的代码时:-

      输出:-

      31536000000
      1471228928
      2147483647
      

      所以,你可以看到区别..

      011101010111101100010010110000000000 -- Binary equivalent of 1000*60*60*24*365L
          01111111111111111111111111111111 -- Binary equivalent of Integer.MAX_VALUE
      

      因此,如果您不在数字末尾添加 L,则会从第一个二进制字符串中删除 4 个最高有效位。

      所以,字符串变成了..

      (0111)01010111101100010010110000000000 -- Remove the most significant bits..
            01010111101100010010110000000000 -- Binary equivalent of 1471228928
      

      (作为输出得到)


      更新:- 从上面的解释,你也可以明白,即使在第一次赋值中,如果你的integersintegers365L相乘之前的结果超出范围,那么它会再次被截断以适应整数范围,或根据需要转换为2's complement representation,然后仅将其与long value - 365L相乘。

      例如:-

      long thirtyYearWithL = 1000*60*60*24*30*365L;
      

      在上面的例子中,考虑第一部分 - 1000*60*60*24*30。这个乘法的结果是:-2592000000。现在让我们看看它在binary equivalent 中是如何表示的:-

      2592000000 = 10011010011111101100100000000000  -- MSB is `1`, a negative value
                   01100101100000010011100000000001  -- 2's complement representation
      

      2's complement 表示的十进制表示是1702967297。因此,2592000000 被转换为 -1702967297,然后乘以 365L。现在,这个值适合integer range,即:-[-2147483648 to 2147483647],所以它不会被进一步截断。

      所以,实际的结果是:-

      long thirtyYearWithL = 1000*60*60*24*30*365L;
                           = 2592000000 * 365L;
                           = -1702967297 * 365L = -621583063040
      

      所以,所有这些东西只是考虑了应用算术运算时最终结果的实际type。并且对从 left to right 移动的每个临时操作结果执行此检查(考虑具有 left-to-right 关联性的运算符)。如果发现任何临时结果超出范围,则将其相应地转换为适合所需范围,然后继续进行下一个操作。


      更新 2:-

      所以,而不是:-

      long thirtyYearWithL = 1000*60*60*24*30*365L;
      

      如果你在开始时移动你的365L,那么你会得到正确的结果:-

      long thirtyYearWithL = 365L*1000*60*60*24*30; // will give you correct result
      

      因为,现在您的temporary 结果将是long 类型,并且能够保存该值。

      【讨论】:

      • @brimborium.. 感谢您对其进行调整以使其更清晰.. :)
      • 这是一个地狱般的解释 :)
      • 我在 SO 上见过的最漂亮清晰的答案之一。
      • 哦,我明白了,所以根据您的进一步解释,我假设将 long 值放在左侧,如 365L*1000*60*60*24*30 将防止乘法结果超过整数值因为每个临时算术运算都将被解释为 long,因为它以 long 开头。我说的对吗?
      • @fareed.. 没错。在这种情况下,您将获得所需的结果。因为,每个临时结果都会是一个长值。伟大的解释。 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 1970-01-01
      • 2011-01-14
      • 1970-01-01
      相关资源
      最近更新 更多