提问者:小点点

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


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

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


共3个答案

匿名用户

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

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

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实用程序方法中。

匿名用户

好吧,就原始整数类型而言,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或只是log,无论什么)。 如果您想检测长操作上的溢出(您不太可能使用原语),请改用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;
}

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

那么这些布尔表达式是如何/为什么工作的呢? 首先,一些逻辑思维揭示,只有当两个论点的符号相同时,才能发生溢出。 因为,如果一个参数为负,一个为正,(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)

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

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

匿名用户

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

从Java 8开始,Java.lang.Math为执行命名操作的int参数和long参数提供了AddExactSubtracTexactMultiplyExactIncrementExactDecrementExactNegateExact静态方法,在溢出时引发ArithmeticException。 (没有divideExact方法--您必须自己检查一个特殊情况(min_value/-1)。)

从Java 8开始,Java.lang.Math还提供ToIntExact将long转换为int,如果long的值不适合int,则抛出ArithmeticException。 这对于例如使用未检查的长数学计算int的和,然后使用ToIntExact在末尾强制转换为int非常有用(但是注意不要让总和溢出)。

如果您还在使用旧版本的Java,Google Guava提供IntMath和LongMath静态方法,用于检查加,减,乘和幂(溢出时抛出)。 这些类还提供了计算因子和二项式系数的方法,这些因子和二项式系数在溢出时返回max_value(检查起来不太方便)。 Guava的基本实用工具类SignedBytesUnsignedBytesShortsInts提供了CheckedCast方法,用于缩小较大的类型(在Under/Overflow时引发IllegalArgumentException,而不是ArithmeticException),还提供了SaturatingCast方法,用于在Overflow时返回Min_Value