两个整数的乘法溢出导致负数
请考虑Java语言规范中的此片段。
class Test { public static void main(String[] args) { int i = 1000000; System.out.println(i * i); long l = i; System.out.println(l * l); } }
输出是
-727379968 1000000000000
为什么结果-727379968
(i*i)
? 理想情况下应该是1000000000000。
我知道Integer的范围是从-2147483648到2147483647.所以显然1000000000000不在给定的范围内。
为什么结果变成-727379968
?
Java(就像现在的大多数计算机体系结构一样)使用称为二进制补码算法的东西,它使用整数的最高有效位来表示数字是负数。 如果你乘以两个大数字,你最终得到一个如此大的数字,它设置最高位,结果最终为负数。
您可能希望将整数溢出检查为一般概念。 根据语言的不同,溢出和下溢的处理方式也不同。 这是一篇关于Java中Integer溢出和下溢的文章。
至于Java语言中的原因,一如既往,它在语言设计的简单性和性能之间进行权衡。 但是在Java的益智游戏(谜题3)中,作者批评了Java中的溢出是静默的:
语言设计者的教训是,可能值得减少无声溢出的可能性 。 这可以通过为不会无声溢出的算术提供支持来完成。 程序可以抛出exception而不是溢出,就像Ada一样,或者它们可以根据需要自动切换到更大的内部表示以避免溢出,就像Lisp一样。 这两种方法都可能存在与之相关的性能损失。 减少静默溢出的可能性的另一种方法是支持目标类型,但这会增加类型系统的显着复杂性。
让我们看看二进制文件:
1000000是1111 0100 0010 0100 0000
。
1000000000000是1110 1000 1101 0100 1010 0101 0001 0000 0000 0000
但是,4位的前两个部分不适合 int
(因为int
在Java中是32位宽),所以它们被删除,只留下1101 0100 1010 0101 0001 0000 0000 0000
,即-727379968
。
换句话说,结果溢出为int
,你得到了剩下的东西。
其他一些答案正确解释了为什么会发生这种情况(即签署两个恭维二进制逻辑)。
问题的实际解决方案以及如何在使用非常大的数字时使用Java获得正确的答案是使用BigInteger类,它也适用于长值。
package com.craigsdickson.scratchpad; import java.math.BigInteger; public class BigIntegerExample { public static void main(String[] args) { int bigInt = Integer.MAX_VALUE; // prints incorrect answer System.out.println(bigInt * bigInt); BigInteger bi = BigInteger.valueOf(bigInt); // prints correct answer System.out.println(bi.multiply(bi)); long bigLong = Long.MAX_VALUE; // prints incorrect answer System.out.println(bigLong * bigLong); BigInteger bl = BigInteger.valueOf(bigLong); // prints correct answer System.out.println(bl.multiply(bl)); } }
整数溢出发生的原因已在其他答案中解释过。
确保计算中长算术的一种实用方法是使用带有l
后缀的数字文字,将文字声明为long
。
溢出的普通整数乘法:
jshell> 100000 * 100000 $1 ==> -727379968
乘法,其中一个被乘数具有l
后缀,不会溢出:
jshell> 100000 * 100000l $1 ==> 1000000000000
请注意, long
s也容易溢出,但范围要大得多,从-9,223,372,036,854,775,808
到9,223,372,036,854,775,807
。