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

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

在此基础上,你将如何检查/测试这种情况的发生?

196850 次浏览

它什么都不做——只是发生了under/overflow。

“-1”是溢出计算的结果,与任何其他信息产生的“-1”没有区别。因此,您不能通过某些状态或仅仅检查一个值来判断它是否溢出。

但是为了避免溢出,你可以聪明地进行计算,如果这很重要的话,或者至少知道什么时候会发生溢出。你的情况如何?

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

您可以事先检查如下:

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代替int来对long执行相同的检查)

如果你认为这种情况经常发生,那么可以考虑使用可以存储更大值的数据类型或对象,例如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;
}
}

源代码分别可以在在这里在这里中找到。

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

它环绕着。

例句:

public class Test {


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

从java8开始,java.lang.Math包有类似addExact()multiplyExact()的方法,当发生溢出时会抛出ArithmeticException

好吧,就基本整数类型而言,Java根本不处理Over/Underflow(对于float和double的行为是不同的,它将刷新到+/-无穷大,就像IEEE-754要求的那样)。

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

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;
}

你会做什么来代替throw子句,这取决于你的应用程序的需求(throw,刷新到最小/最大或只是记录)。如果您想检测长操作上的溢出,则无法使用原语,请使用BigInteger代替。


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

它基本上是一个布尔表达式,包含两个操作数的符号以及结果:

/**
* 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中,更简单的方法是将表达式(在if中)应用到整个32位,并使用<0(这将有效地测试符号位)。所有整数基元类型的工作原理完全相同,将上述方法中的所有声明更改为long,使其工作为long。

对于较小的类型,由于隐式转换为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;
}

(注意上面的例子使用表达式need进行减去溢出检测)


那么这些布尔表达式是如何/为什么工作的呢?首先,一些逻辑思维揭示,如果两个参数的符号相同,就会发生只有溢出。因为,如果一个参数为负,另一个参数为正,则add 必须的结果接近于零,或者在极端情况下,一个参数为零,与另一个参数相同。由于参数本身不能创建溢出条件,它们的和也不能创建溢出。

如果两个参数的符号相同会怎样?让我们来看看两者都为正的情况:添加两个参数,创建一个大于类型MAX_VALUE的和,将总是产生一个负值,因此溢出发生如果 arg1 + arg2 > MAX_VALUE。现在可能产生的最大值将是MAX_VALUE + MAX_VALUE(极端情况下两个参数都是MAX_VALUE)。对于一个字节(示例),这意味着127 + 127 = 254。查看由两个正数值相加产生的所有值的位表示,可以发现溢出的(128到254)都设置了第7位,而所有不溢出的(0到127)都清除了第7位(最上面的符号)。这正是表达式的第一部分(右边)所检查的:

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

(~年代,~ d,r)为真,只有在,两个操作数(s, d)为正,结果(r)为负(该表达式适用于所有32位,但我们唯一感兴趣的位是最上面的(符号)位,由<0)。

现在,如果两个参数都是负的,它们的和永远不会比任何一个参数更接近于零,必须的和更接近于负无穷。我们可以生成的最极端的值是MIN_VALUE + MIN_VALUE,它(同样是字节示例)表明对于范围内的任何值(-1到-128)都设置了符号位,而任何可能的溢出值(-129到-256)都清除了符号位。因此,结果的符号再次显示了溢出条件。这就是左半部分(s &维,~r)检查两个参数(s, d)都为负而结果为正的情况。逻辑在很大程度上等同于积极的情况;所有由两个负数相加产生的位模式都将清除符号位当且仅当,如果下溢发生。

我认为你应该使用这样的东西,它被称为Upcasting:

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;
}

你可以在这里进一步阅读: 检测或防止整数溢出 < / p >

这是相当可靠的来源。

我自己也遇到了这个问题,下面是我的解决方案(包括乘法和加法):

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;
}

如果有错误或者可以简化,请随意纠正。我已经用乘法法做了一些测试,大部分是边缘情况,但它仍然可能是错误的。

Java对int或长基元类型的整数溢出没有做任何处理,而是忽略正整数和负整数溢出。

这个答案首先描述了整数溢出的原因,给出了一个例子,说明了它是如何发生的,即使是表达式求值中的中间值,然后给出了一些资源的链接,这些资源提供了防止和检测整数溢出的详细技术。

整数算术和表达式导致意外或未检测到的溢出是常见的编程错误。意外或未检测到的整数溢出也是一个众所周知的可利用的安全问题,特别是当它影响数组、堆栈和列表对象时。

溢出可能以正或负的方向发生,其中正或负的值将超过所讨论的基元类型的最大值或最小值。在表达式或操作求值期间,中间值可能发生溢出,并影响最终值在范围内的表达式或操作的结果。

有时负溢出被错误地称为下溢。下溢是指当一个值比表示法允许的值更接近于零时所发生的情况。下溢发生在整数运算中,是预期的。整数下溢发生在整数计算介于-1到0或0到1之间时。小数的结果被截断为0。对于整数运算,这是正常的,也是预期的,不被认为是错误。但是,它可能导致代码抛出异常。一个例子是,如果在表达式中使用整数下流的结果作为除数,则会出现“ArithmeticException: / by zero”异常。

考虑下面的代码:

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

这将导致x被赋值为0,并且bigValue / x的后续求值会抛出一个异常,“ArithmeticException: / by zero”(即除以0),而不是将y赋值为2。

x的预期结果是858,993,458,小于最大int值2,147,483,647。但是,计算Integer的中间结果。MAX_Value * 2,将是4,294,967,294,它超过了最大int值,并根据2s补全整数表示为-2。然后-2 / 5的结果是0,赋值给x。

将计算x的表达式重新排列为在求值时先除后乘的表达式,如下代码:

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的补码整数值。在没有明确设计为使用2s补码行为的情况下(大多数普通整数算术情况),所得到的2s补码值将导致编程逻辑或计算错误,如上面的示例所示。维基百科上有一篇很棒的文章描述了2个补二进制整数:二的补充——维基百科

有一些技术可以避免意外的整数溢出。技术可以分为使用前置条件测试、向上转换和BigInteger。

前置条件测试包括检查进入算术运算或表达式的值,以确保这些值不会发生溢出。编程和设计将需要创建测试,以确保输入值不会导致溢出,然后确定如果发生导致溢出的输入值该怎么办。

上强制转换包括使用较大的基元类型来执行算术操作或表达式,然后确定结果值是否超过整数的最大值或最小值。即使使用上强制转换,操作或表达式中的值或某些中间值仍有可能超出上强制转换类型的最大值或最小值,并导致溢出,这也不会被检测到,并将导致意外和不希望看到的结果。通过分析或前提条件,当不进行上浇而进行溢流预防是不可能或不实际的时候,有可能通过上浇来防止溢流。如果所讨论的整数已经是长基元类型,那么在Java中就不可能使用基元类型进行上转换。

BigInteger技术包括使用BigInteger进行算术操作或使用使用BigInteger的库方法进行表达式。BigInteger不溢出。如果需要,它将使用所有可用内存。它的算术方法通常只比整数运算的效率略低。使用BigInteger的结果仍然有可能超出整数的最大值或最小值,但是,在导致该结果的算术中不会发生溢出。编程和设计仍然需要确定,如果BigInteger结果超出所需原语结果类型(例如int或long)的最大值或最小值,该怎么办。

卡内基梅隆软件工程研究所的CERT程序和Oracle已经为安全Java编程创建了一套标准。这些标准包括防止和检测整数溢出的技术。该标准以免费在线资源的形式发布:CERT Oracle Java安全编码标准

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

图书形式和PDF形式的CERT Oracle Java安全编码标准也可用。

有一些库提供安全的算术操作,用于检查整数溢出/下溢。例如,Guava的IntMath。checkedAdd(int a, int b)返回ab的和,前提是它没有溢出,如果a + b在有符号的int算术中溢出,则抛出ArithmeticException

默认情况下,Java的int和long数学在溢出和下溢时默默地环绕。(对其他整数类型的整型操作是通过根据JLS 4.2.2首先将操作数提升为int或long来执行的。)

从Java 8开始,java.lang.Math为int和long参数提供了addExactsubtractExactaddExact0、addExact1、addExact2和addExact3静态方法,用于执行命名操作,在溢出时抛出arithmeexception。(没有divideExact方法——你必须自己检查一个特殊情况(MIN_VALUE / -1)。)

从Java 8开始,Java .lang. math还提供了toIntExact来将long类型转换为int类型,如果long类型的值不适合int类型,则抛出算术异常。这对于例如使用未检查的长数学计算int类型的和,然后在最后使用toIntExact强制转换为int类型很有用(但要注意不要让你的和溢出)。

如果你仍在使用旧版本的Java,谷歌Guava提供IntMath和LongMath静态方法用于检查加、减、乘和取幂(在溢出时抛出)。这些类还提供了计算阶乘和二项式系数的方法,这些方法在溢出时返回MAX_VALUE(不太方便检查)。Guava的基本实用程序类SignedBytesUnsignedBytesShortsInts,提供了checkedCast方法用于缩小较大类型(在/overflow下抛出IllegalArgumentException, SignedBytes0 arithticexception),以及saturatingCast方法,在溢出时返回MIN_VALUEMAX_VALUE

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

这个案例在这里讨论: 整数溢出产生零。 < / p >

我想这应该没问题。

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