为什么这两个乘法操作会得出不同的结果?

37

为什么我需要添加“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
2个回答

138
long oneYearWithL = 1000*60*60*24*365L;
long oneYearWithoutL = 1000*60*60*24*365;

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

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

看看以下的打印语句:-

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

(你将其作为输出获得)


更新: - 从上面的解释中,您也可以理解,在第一次赋值时,如果您的整数乘法的结果在乘以 365L 之前超出范围,那么它将再次被截断以适合整数范围,或者如果需要,则转换为 2的补码表示,然后才会将它乘以 long value - 365L

例如:-

long thirtyYearWithL = 1000*60*60*24*30*365L;
在上面的示例中,考虑第一部分 - 1000*60*60*24*30。这个乘积的结果是:- 2592000000。现在让我们看看它在二进制等价物中表示为何:-
2592000000 = 10011010011111101100100000000000  -- MSB is `1`, a negative value
             01100101100000010011100000000001  -- 2's complement representation

二进制补码的十进制表示为1702967297。因此,在将2592000000乘以365L之前,它被转换为-1702967297。现在,由于此值适合整数范围:-[-2147483648 to 2147483647],因此不会进一步截断。

因此,实际结果将是:-

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

因此,所有这些东西只考虑应用算术运算后最终结果的实际类型。并且在移动操作时逐个检查每个临时结果,从左到右(考虑具有从左到右结合性的运算符)。如果发现任何临时结果超出范围,则会相应地转换为适合所需范围,然后再进行下一个操作。


更新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类型,并且能够容纳该值。


1
这真是一次精彩的解释 :) - PermGenError
4
我在SO上看到的最清晰明了的答案之一。 - Gunnar Karlsson
1
哦,我明白了。根据你的进一步解释,我认为将长值放在左边,例如365L1000606024*30,将防止乘法结果超过整数值,因为每个临时算术操作都将被解释为长整型,因为它以长整型开始。我的理解正确吗? - fareed
1
@fareed.. 没错。在这种情况下,您将获得所需的结果。因为每个临时结果都将是一个长整型值。很好的解释。 :) - Rohit Jain
@RohitJain,非常好的答案,提供了有价值的信息和解释。肯定+1 :) - Ritesh Gune
显示剩余3条评论

15

如果没有使用L,计算结果将是一个32位的值。如果将值用十六进制表示,那么较小的值就是较大值的低4字节。

Java默认使用32位整数类型。使用L表示长整型,它有64位。当在32位和64位的值相乘时,编译器会将32位值向64位转换,以便你在计算表达式时保留全部的64位范围。

请查看Java语言规范中的基本类型和值

乘法运算的行为在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)。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接