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

2022-08-31 05:32:53

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

从这一点开始,您将如何检查/测试这种情况是否正在发生?


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

(您可以将 int 替换为 long长时间执行相同的检查)

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


如果你碰巧已经在Java8上,那么你可以使用新的Math#addExact()Math#subtractExact()方法,它们会引发一个 on overflow。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


答案 2

好吧,就原始整数类型而言,Java根本不处理Over/Underflow(对于浮点和双精度,行为是不同的,它将刷新到+/- 无穷大,就像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位,并使用<0检查结果(这将有效地测试符号位)更简单。该原则对于所有整数基元类型的工作方式完全相同,将上述方法中的所有声明更改为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;
}

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


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

那么,如果两个参数具有相同的符号,会发生什么情况呢?让我们看一下两者都是正数的情况:添加两个参数以创建大于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)

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

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