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

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


当前回答

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

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

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

其他回答

它环绕着。

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

从java8开始,java.lang.Math包就有了addExact()和multiplyExact()这样的方法,它们会在发生溢出时抛出一个ArithmeticException。

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

从Java 8开始,Java .lang. math为执行命名操作的int和long参数提供了addExact、subtractExact、multiplyExact、incrementExact、decrementExact和negateExact静态方法,并在溢出时抛出arithmeexception。(没有divideExact方法——您必须自己检查一个特殊情况(MIN_VALUE / -1)。)

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

If you're still using an older version of Java, Google Guava provides IntMath and LongMath static methods for checked addition, subtraction, multiplication and exponentiation (throwing on overflow). These classes also provide methods to compute factorials and binomial coefficients that return MAX_VALUE on overflow (which is less convenient to check). Guava's primitive utility classes, SignedBytes, UnsignedBytes, Shorts and Ints, provide checkedCast methods for narrowing larger types (throwing IllegalArgumentException on under/overflow, not ArithmeticException), as well as saturatingCast methods that return MIN_VALUE or MAX_VALUE on overflow.

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

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

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

好吧,就基本整数类型而言,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;
}

(注意,上面的例子使用了表达式需要进行减法溢出检测)


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

So what happens if both arguments have the same sign? Lets take a look at the case both are positive: adding two arguments that create a sum larger than the types MAX_VALUE, will always yield a negative value, so an overflow occurs if arg1 + arg2 > MAX_VALUE. Now the maximum value that could result would be MAX_VALUE + MAX_VALUE (the extreme case both arguments are MAX_VALUE). For a byte (example) that would mean 127 + 127 = 254. Looking at the bit representations of all values that can result from adding two positive values, one finds that those that overflow (128 to 254) all have bit 7 set, while all that do not overflow (0 to 127) have bit 7 (topmost, sign) cleared. Thats exactly what the first (right) part of the expression checks:

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

(~s & ~d & r)为真,仅当两个操作数(s, d)为正且结果(r)为负时才为真(该表达式适用于所有32位,但我们唯一感兴趣的位是最上面的(符号)位,它由< 0检查)。

Now if both arguments are negative, their sum can never be closer to zero than any of the arguments, the sum must be closer to minus infinity. The most extreme value we can produce is MIN_VALUE + MIN_VALUE, which (again for byte example) shows that for any in range value (-1 to -128) the sign bit is set, while any possible overflowing value (-129 to -256) has the sign bit cleared. So the sign of the result again reveals the overflow condition. Thats what the left half (s & d & ~r) checks for the case where both arguments (s, d) are negative and a result that is positive. The logic is largely equivalent to the positive case; all bit patterns that can result from adding two negative values will have the sign bit cleared if and only if an underflow occured.

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