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

时间:2010-06-08 22:37:17

标签: java integer overflow robustness correctness

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

在此之后,您将如何检查/测试这种情况?

12 个答案:

答案 0 :(得分:201)

如果它溢出,它会返回minimum value并从那里继续。如果它下溢,它会返回maximum value并从那里继续。

您可以按照以下方式预先检查:

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

(您可以int替换long来对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;
    }
}

源代码分别可以找到herehere

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

答案 1 :(得分:64)

好吧,就原始整数类型而言,Java根本不处理上溢/下溢(对于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,flush to min / max或者只记录任何东西)。如果你想在长时间的操作中检测溢出,你就会对原语失去兴趣,而是使用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位更简单,并使用&lt;检查结果。 0(这将有效地测试符号位)。对于所有整数基元类型,该原理的作用完全相同,将上述方法中的所有声明更改为long使其长时间工作。

对于较小的类型,由于隐式转换为int(有关详细信息,请参阅JLS以获取按位操作),而不是检查&lt; 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;
}

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


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

那么如果两个参数具有相同的符号会发生什么?让我们看看两者都是正面的情况:添加两个创建大于MAX_VALUE类型的和的参数将始终产生负值,因此如果 arg1 + arg2&gt;则发生溢出 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)

(〜S&安培;〜d&安培; R)变为真,仅当,两个操作数(S,d)是正的,结果(r)是负的(表达式适用于所有32位,但我们感兴趣的唯一位是最顶层(符号)位,由&lt; 0)检查。

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

答案 2 :(得分:31)

对于int或long基本类型,Java不会对整数溢出执行任何操作,并忽略具有正整数和负整数的溢出。

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

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

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

有时负溢出被误称为下溢。下溢是当值接近零而不是表示允许时发生的情况。下溢发生在整数运算中并且是预期的。当整数评估介于-1和0或0和1之间时,会发生整数下溢。分数结果将截断为0.这是正常的,预期使用整数运算,不会被视为错误。但是,它可能导致代码抛出异常。一个例子是&#34; ArithmeticException:/ by zero&#34;如果整数下溢的结果用作表达式中的除数,则为异常。

请考虑以下代码:

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

导致x被赋值0并且随后对bigValue / x的评估抛出异常,&#34; ArithmeticException:/ by zero&#34; (即除以零),而不是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补码值将导致编程逻辑或计算错误,如上例所示。一篇优秀的维基百科文章在这里描述了2s补充二进制整数:Two's complement - Wikipedia

有一些技术可以避免无意的整数溢出。 Techinques可以分类为使用前置条件测试,upcasting和BigInteger。

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

向上转换包括使用较大的基元类型来执行算术运算或表达式,然后确定结果值是否超出整数的最大值或最小值。即使使用向上转换,操作或表达式中的值或某个中间值仍可能超出upcast类型的最大值或最小值,并导致溢出,这也将无法检测到,并将导致意外和不希望的结果。通过分析或前提条件,可以防止在不进行预制或不实际预测的情况下进行预防溢流。如果有问题的整数已经是长原始类型,那么Java中的原始类型无法进行向上转换。

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

卡内基梅隆软件工程研究所的CERT计划和Oracle为安全Java编程创建了一套标准。标准中包括用于防止和检测整数溢出的技术。该标准在此处作为可免费访问的在线资源发布:The CERT Oracle Secure Coding Standard for Java

标准部分描述并包含用于防止或检测整数溢出的编码技术的实际示例:NUM00-J. Detect or prevent integer overflow

还提供了CERT Oracle安全编码标准的书形式和PDF格式。

答案 3 :(得分:30)

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

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

从Java 8开始,java.lang.Math还提供toIntExact来将一个long转换为int,如果long的值不适合int,则抛出ArithmeticException。这可用于例如使用未经检查的长数学计算整数的总和,然后使用toIntExact在结尾处转换为int(但要注意不要让你的总和溢出)。

如果您仍在使用旧版本的Java,Google Guava会提供IntMath and LongMath静态方法来检查加法,减法,乘法和取幂(抛出溢出)。这些类还提供了计算因子(溢出时返回MAX_VALUE的因子和二项式系数的方法(这不太方便检查)。 Guava的原始实用工具类SignedBytesUnsignedBytesShortsInts提供了缩小较大类型的checkedCast方法(在/下抛出IllegalArgumentException)溢出, ArithmeticException),以及在溢出时返回saturatingCastMIN_VALUE的{​​{1}}方法。

答案 4 :(得分:12)

我自己也遇到过这个问题,这是我的解决方案(用于乘法和加法):

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

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

答案 5 :(得分:8)

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

答案 6 :(得分:6)

它包裹着。

e.g:

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

答案 7 :(得分:5)

我认为你应该使用这样的东西,它被称为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;
}

您可以在这里阅读: Detect or prevent integer overflow

这是非常可靠的来源。

答案 8 :(得分:3)

它没有做任何事情 - 发生了欠/溢出。

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

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

答案 9 :(得分:1)

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

答案 10 :(得分:1)

我认为这应该没问题。

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

答案 11 :(得分:0)

有一个案例,上面没有提到:

int res = 1;
while (res != 0) {
    res *= 2;

}
System.out.println(res);

将产生:

0

这个案例在这里讨论: Integer overflow produces Zero.