Java 如何处理整数下溢和上溢,以及如何检查它?
Java 如何处理整数下溢和上溢?
从这一点开始,您将如何检查/测试这种情况是否正在发生?
Java 如何处理整数下溢和上溢?
从这一点开始,您将如何检查/测试这种情况是否正在发生?
如果它溢出,它将返回到最小值并从那里继续。如果它下溢,它将返回到最大值并从那里继续。
您可以事先检查,如下所示:
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
好吧,就原始整数类型而言,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)都是负的,结果是正数的情况。逻辑在很大程度上等同于正情况;当且仅当发生下溢时,添加两个负值可能产生的所有位模式都将清除符号位。