两个整数的乘法溢出导致负数

请考虑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,8089,223,372,036,854,775,807