Java如何处理整数下溢和溢出,你将如何检查它?

Java如何处理整数下溢和溢出?

由此导致,你将如何检查/testing,这是发生?

如果溢出,则返回最小值并从那里继续。 如果下溢,则回到最大值并从那里继续。

您可以事先检查如下:

 public static boolean willAdditionOverflow(int left, int right) { if (right < 0 && right != Integer.MIN_VALUE) { return willSubtractionOverflow(left, -right); } else { return (~(left ^ right) & (left ^ (left + right))) < 0; } } public static boolean willSubtractionOverflow(int left, int right) { if (right < 0) { return willAdditionOverflow(left, -right); } else { return ((left ^ right) & (left ^ (left - right))) < 0; } } 

(你可以用long来代替intlong执行相同的检查)

如果您认为这种情况可能会经常发生,请考虑使用可存储较大值的数据types或对象,例如longjava.math.BigInteger 。 最后一个不会溢出,实际上,可用的JVM内存是限制。


如果你碰巧在Java8上,那么你可以使用新的Math#addExact()Math#subtractExact()方法,这会在溢出时抛出ArithmeticException

 public static boolean willAdditionOverflow(int left, int right) { try { Math.addExact(left, right); return false; } catch (ArithmeticException e) { return true; } } public static boolean willSubtractionOverflow(int left, int right) { try { Math.subtractExact(left, right); return false; } catch (ArithmeticException e) { return true; } } 

源代码可以在这里和这里分别find。

当然,你也可以直接使用它们,而不是将它们隐藏在一个boolean实用程序方法中。

那么,就原始的整数types而言,Java根本不处理溢出/下溢(对于float和double来说,行为是不同的,就像IEEE-754所要求的那样,它将冲刷到+/-无穷大)。

当添加两个int时,发生溢出时不会有任何提示。 检查溢出的一个简单方法是使用下一个更大的types来实际执行操作,并检查结果是否仍然在源types的范围内:

 public int addWithOverflowCheck(int a, int b) { // the cast of a is required, to make the + work with long precision, // if we just added (a + b) the addition would use int precision and // the result would be cast to long afterwards! long result = ((long) a) + b; if (result > Integer.MAX_VALUE) { throw new RuntimeException("Overflow occured"); } else if (result < Integer.MIN_VALUE) { throw new RuntimeException("Underflow occured"); } // at this point we can safely cast back to int, we checked before // that the value will be withing int's limits return (int) result; } 

你会做什么来代替抛出子句,取决于你的应用程序的要求(扔,刷新到最小/最大或只是logging什么)。 如果你想在长时间的操作中检测到​​溢出,那么对于基元来说就不是那么幸运了,而是使用BigInteger。


编辑(2014-05-21):由于这个问题似乎很频繁地提到,我不得不自己解决同样的问题,用CPU计算V标志的相同方法来评估溢出情况是相当容易的。

它基本上是一个布尔expression式,涉及两个操作数的符号以及结果:

 /** * Add two int's with overflow detection (r = s + d) */ public static int add(final int s, final int d) throws ArithmeticException { int r = s + d; if (((s & d & ~r) | (~s & ~d & r)) < 0) throw new ArithmeticException("int overflow add(" + s + ", " + d + ")"); return r; } 

在java中,将expression式(在if中)应用于整个32位更简单,并使用<0(这将有效地testing符号位)检查结果。 对于所有的整数基本types ,原理都是一样的,将上面的方法中的所有声明都改为long,使得它长时间工作。

对于较小的types,由于隐式转换为int(有关详细信息,请参阅JLS的按位操作),而不是检查<0,而是需要显式掩码符号位(对于短操作数,为0x8000,对于字节操作数为0x80,和参数声明):

 /** * Subtract two short's with overflow detection (r = d - s) */ public static short sub(final short d, final short s) throws ArithmeticException { int r = d - s; if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0) throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")"); return (short) r; } 

(注意上面的例子使用了expression式需要减去溢出检测)


那么这些布尔expression式是如何工作的呢? 首先,一些逻辑思维表明, 只有两个参数的符号相同才能发生溢出。 因为如果一个参数是负数和一个正数,那么(加法)的结果必须接近于零,或者在极端情况下一个参数是零,与另一个参数相同。 由于参数本身不能产生溢出条件,所以它们的总和也不能产生溢出。

那么如果两个参数具有相同的符号会发生什么? 让我们看看这两种情况都是肯定的:添加两个参数来创build大于MAX_VALUEtypes的总和,总是产生一个负值,所以如果 arg1 + arg2> MAX_VALUE,就会发生溢出。 现在可能产生的最大值将是MAX_VALUE + MAX_VALUE(这两个参数都是MAX_VALUE的极端情况)。 对于一个表示127 + 127 = 254的字节(示例)来看看添加两个正值可能导致的所有值的位表示,发现那些溢出(128到254)都有位7置位,而所有不溢出(0到127)都有位7(最高,符号)清除。 这正是expression式的第一个(右)部分所检查的内容:

 if (((s & d & ~r) | (~s & ~d & r)) < 0) 

(s&d&r)成立时, 只有当两个操作数(s,d)都是正数,结果(r)是负数(expression式适用于所有32位,但是我们感兴趣的唯一一点是最高的(符号)位,由<0)检查。

现在,如果两个参数都是负的,它们的总和就不可能比任何参数都接近零,总和必须接近负无穷大。 我们可以产生的最极端的值是MIN_VALUE + MIN_VALUE,对于任何范围值(-1到-128)来说(对于字节例子来说)符号位被设置,而任何可能的溢出值(-129到-256 )已经清除了符号位。 所以结果的标志再次显示溢出情况。 那就是左半部分(s&d&r)检查两个参数(s,d)是否定的,而结果是正的。 逻辑在很大程度上等同于正面的情况。 当且仅当发生下溢时,可能由于添加两个负值而产生的所有位模式都会将符号位清零。

对于int或long基本types,Java不做任何整数溢出,而忽略正整数和负整数的溢出。

这个答案首先描述了整数溢出问题,给出了一个例子,说明如何在expression式评估中使用中间值,然后给出资源的链接,给出了防止和检测整数溢出的详细技术。

整数算术和expression式意外或未检测溢出是一个常见的编程错误。 意外或未检测到的整数溢出也是众所周知的可利用的安全问题,尤其是因为它影响数组,堆栈和列表对象。

溢出可能出现正方向或负方向,其中正值或负值将超出所讨论的基元types的最大值或最小值。 在expression或操作评估过程中,溢出可能发生在中间值,并影响最终值预期在范围内的expression式或操作的结果。

有时候,负溢出被误称为下溢。 下溢是当值比表示允许时更接近于零的情况。 下溢出现在整数算术中,并且是预期的。 当整数求值在-1和0或0和1之间时,发生整数下溢。什么是小数结果截断为0.这是正常的,并且用整数算术期望并且不被认为是错误。 但是,它可能会导致代码抛出exception。 如果将整数下溢的结果用作expression式的除数,则会出现一个“ArithmeticException:/ by zero”exception。

考虑下面的代码:

 int bigValue = Integer.MAX_VALUE; int x = bigValue * 2 / 5; int y = bigValue / x; 

这导致x被分配0,并且随后的对bigValue / x的评估抛出exception“ArithmeticException:/ by零”(即,除以零),而不是将y分配给值2。

x的预期结果是858,993,458,小于最大整数值2,147,483,647。 然而,评估Integer.MAX_Value * 2的中间结果将是4,294,967,294,其超过了最大的int值,并且根据二进制补码整数表示是-2。 随后评估-2 / 5评估为0,其被分配给x。

将计算x的expression式重新排列为一个expression式,在求值时,除以下之前的代码:

 int bigValue = Integer.MAX_VALUE; int x = bigValue / 5 * 2; int y = bigValue / x; 

导致x被分配858,993,458,y被分配2,这是预期的。

bigValue / 5的中间结果是429,496,729,它不超过int的最大值。 后续对429,496,729 * 2的评估不超过int的最大值,并将预期结果分配给x。 对y的评估则不会被零除。 x和y的评估按预期工作。

Java整数值存储为并按照2s补码有符号整数表示进行操作。 当结果值大于或小于最大或最小整数值时,反而会得到2的补码整数值。 在没有明确devise使用二进制补码行为(最常见的整数算术情况)的情况下,得到的二进制补码值将导致编程逻辑或计算错误,如上例所示。 一篇优秀的维基百科文章在这里描述2s恭维二进制整数:二进制补码 – 维基百科

有技巧可以避免无意的整数溢出。 技术可以分为使用前置条件testing,上传和BigInteger。

预条件testing包括检查进入算术运算或expression式的值,以确保这些值不会发生溢出。 编程和devise将需要创buildtesting,确保input值不会导致溢出,然后确定如果input值发生会导致溢出该怎么做。

上溯包括使用较大的基元types来执行算术运算或expression式,然后确定结果值是否超出整数的最大值或最小值。 即使在上传过程中,操作或expression式中的值或某个中间值仍有可能超出upcasttypes的最大值或最小值,并导致溢出,这也将不会被检测到,并且会导致意想不到的结果。 通过分析或预先条件,在没有上传的预防不可能或不实际的情况下,有可能防止上传溢出。 如果所讨论的整数已经是长基元types,那么Java中的原始types就不可能进行向上转换。

BigInteger技术包括使用BigInteger进行算术运算或使用使用BigInteger的库方法的expression式。 BigInteger不会溢出。 如有必要,它将使用所有可用内存。 它的算术方法通常只比整数运算稍微低效。 使用BigInteger的结果仍然有可能超出整数的最大值或最小值,但是在导致结果的算术中不会发生溢出。 编程和devise仍然需要确定如果BigInteger结果超出所需基元结果types的最大值或最小值(例如int或long),该怎么办。

卡内基梅隆软件工程学院的CERT程序和Oracle已经创build了一套安全的Java编程标准。 标准中包括防止和检测整数溢出的技术。 该标准作为一个免费的在线资源发布在这里: CERT Oracle Java安全编码标准

描述并包含用于防止或检测整数溢出的编码技术的实际示例的标准部分在这里是: NUM00-J。 检测或防止整数溢出

CERT的Oracle安全编码标准Java版书籍表格和PDF格式也可用。

默认情况下,Java的int和longmath运算在上下文中静静地回绕。 (对其他整数types的整型操作是通过首先将操作数提升为int或long来执行的,即每个JLS 4.2.2) 。

从Java 8起, java.lang.Math为执行指定操作的int和long参数提供addExactsubtractExactmultiplyExactincrementExactdecrementExactnegateExact静态方法,并在溢出时抛出ArithmeticException。 (没有divideExact方法 – 您必须自己检查一个特殊情况( MIN_VALUE / -1 )。)

从Java 8开始,java.lang.Math还提供了toIntExact来将long转换为int,如果long的值不适合int,则抛出ArithmeticException。 这可以用于例如使用未经检查的长math计算整数的总和,然后使用toIntExact在最后将其转换为整数(但要小心不要让总和溢出)。

如果您仍在使用旧版本的Java,Google Guava将为IntMath和LongMath提供静态方法,用于检查加法,减法,乘法和指数(抛出溢出)。 这些类还提供了计算溢出时返回MAX_VALUE阶乘和二项式系数的方法(这不便于检查)。 Guava的原始工具类SignedBytesUnsignedBytesShortsInts提供checkedCast方法来缩小较大的types(在溢出时抛出IllegalArgumentException, 而不是 ArithmeticException),以及在溢出时返回MIN_VALUEMAX_VALUE checkedCast方法。

刚刚碰到这个问题我自己,这是我的解决scheme(乘法和加法):

 static boolean wouldOverflowOccurwhenMultiplying(int a, int b) { // If either a or b are Integer.MIN_VALUE, then multiplying by anything other than 0 or 1 will result in overflow if (a == 0 || b == 0) { return false; } else if (a > 0 && b > 0) { // both positive, non zero return a > Integer.MAX_VALUE / b; } else if (b < 0 && a < 0) { // both negative, non zero return a < Integer.MAX_VALUE / b; } else { // exactly one of a,b is negative and one is positive, neither are zero if (b > 0) { // this last if statements protects against Integer.MIN_VALUE / -1, which in itself causes overflow. return a < Integer.MIN_VALUE / b; } else { // a > 0 return b < Integer.MIN_VALUE / a; } } } boolean wouldOverflowOccurWhenAdding(int a, int b) { if (a > 0 && b > 0) { return a > Integer.MAX_VALUE - b; } else if (a < 0 && b < 0) { return a < Integer.MIN_VALUE - b; } return false; } 

随时纠正,如果错误或如果可以简化。 我已经用乘法的方法做了一些testing,主要是边缘情况,但是它可能仍然是错误的。

有一些库提供安全的算术运算,检查整数溢出/下溢。 例如,Guava的IntMath.checkedAdd(int a,int b)返回ab的总和,前提是它不溢出,如果a + b在signed intalgorithm中溢出则抛出ArithmeticException

我想你应该使用这样的东西,它被称为上传:

 public int multiplyBy2(int x) throws ArithmeticException { long result = 2 * (long) x; if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE){ throw new ArithmeticException("Integer overflow"); } return (int) result; } 

您可以在这里进一步阅读: 检测或防止整数溢出

这是相当可靠的来源。

它什么都不做 – 下/溢出只是发生。

作为溢出计算结果的“-1”与其他任何信息产生的“-1”没有什么不同。 所以你不能通过某种状态或通过检查一个值是否溢出来判断。

但是,如果重要,或者至less知道什么时候会发生,你可以聪明地对待计算,以避免溢出。 你的情况如何?

它环绕。

例如:

公共类testing{

 public static void main(String[] args) { int i = Integer.MAX_VALUE; int j = Integer.MIN_VALUE; System.out.println(i+1); System.out.println(j-1); } 

}

版画

-2147483648

2147483647

 static final int safeAdd(int left, int right) throws ArithmeticException { if (right > 0 ? left > Integer.MAX_VALUE - right : left < Integer.MIN_VALUE - right) { throw new ArithmeticException("Integer overflow"); } return left + right; } static final int safeSubtract(int left, int right) throws ArithmeticException { if (right > 0 ? left < Integer.MIN_VALUE + right : left > Integer.MAX_VALUE + right) { throw new ArithmeticException("Integer overflow"); } return left - right; } static final int safeMultiply(int left, int right) throws ArithmeticException { if (right > 0 ? left > Integer.MAX_VALUE/right || left < Integer.MIN_VALUE/right : (right < -1 ? left > Integer.MIN_VALUE/right || left < Integer.MAX_VALUE/right : right == -1 && left == Integer.MIN_VALUE) ) { throw new ArithmeticException("Integer overflow"); } return left * right; } static final int safeDivide(int left, int right) throws ArithmeticException { if ((left == Integer.MIN_VALUE) && (right == -1)) { throw new ArithmeticException("Integer overflow"); } return left / right; } static final int safeNegate(int a) throws ArithmeticException { if (a == Integer.MIN_VALUE) { throw new ArithmeticException("Integer overflow"); } return -a; } static final int safeAbs(int a) throws ArithmeticException { if (a == Integer.MIN_VALUE) { throw new ArithmeticException("Integer overflow"); } return Math.abs(a); } 

有一种情况,上面没有提到:

 int res = 1; while (res != 0) { res *= 2; } System.out.println(res); 

会产生:

 0 

这里讨论了这种情况: 整数溢出产生零。

我认为这应该没问题。

 static boolean addWillOverFlow(int a, int b) { return (Integer.signum(a) == Integer.signum(b)) && (Integer.signum(a) != Integer.signum(a+b)); }