如果溢出,则返回最小值并从那里继续。 如果下溢,则返回到最大值并从那里继续。
您可以按以下方式预先检查:
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
执行相同的检查)
如果您认为这种情况可能经常发生,那么可以考虑使用能够存储较大值的数据类型或对象,例如long
或java.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位,并使用
对于较小的类型,由于隐式到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参数提供了AddExact
,SubtracTexact
,MultiplyExact
,IncrementExact
,DecrementExact
和NegateExact
静态方法,在溢出时引发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的基本实用工具类SignedBytes
,UnsignedBytes
,Shorts
和Ints
提供了CheckedCast
方法,用于缩小较大的类型(在Under/Overflow时引发IllegalArgumentException,而不是ArithmeticException),还提供了SaturatingCast
方法,用于在Overflow时返回Min_Value
或