如何检查在 Java 中两个数相乘是否会导致溢出?

我想处理两个数字相乘导致溢出的特殊情况。代码如下所示:

int a = 20;
long b = 30;


// if a or b are big enough, this result will silently overflow
long c = a * b;

这是个简化版本。在实际的程序中,ab在运行时来源于其他地方。我想达到的目标是这样的:

long c;
if (a * b will overflow) {
c = Long.MAX_VALUE;
} else {
c = a * b;
}

你建议我如何最好地编写这个代码?

Update: a and b are always non-negative in my scenario.

39995 次浏览

也许:

if(b!= 0 && a * b / b != a) //overflow

不太确定这个“解决方案”。

编辑: 增加 b! = 0。

在您下调 之前: a * b/b 不会被优化。这可能是编译器错误。我仍然没有看到可以掩盖溢出错误的情况。

You could use java.math.BigInteger instead and check the size of the result (haven't tested the code):

BigInteger bigC = BigInteger.valueOf(a) * multiply(BigInteger.valueOf(b));
if(bigC.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
c = Long.MAX_VALUE;
} else {
c = bigC.longValue()
}

使用对数检查结果的大小。

Java 有类似 int. MaxValue 的东西吗? 如果有,那么尝试一下

if (b != 0 && Math.abs(a) > Math.abs(Long.MAX_VALUE / b))
{
// it will overflow
}

编辑: 见 Long.MAX _ VALUE 有问题

如果 ab都是阳性,那么你可以使用:

if (a != 0 && b > Long.MAX_VALUE / a) {
// Overflow
}

如果你需要同时处理正数和负数,那就更复杂了:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;


if (a != 0 && (b > 0 && b > maximum / a ||
b < 0 && b < maximum / a))
{
// Overflow
}

这里有一个小表,我迅速地检查这一点,假设溢出发生在 -10或 + 10:

a =  5   b =  2     2 >  10 /  5
a =  2   b =  5     5 >  10 /  2
a = -5   b =  2     2 > -10 / -5
a = -2   b =  5     5 > -10 / -2
a =  5   b = -2    -2 < -10 /  5
a =  2   b = -5    -5 < -10 /  2
a = -5   b = -2    -2 <  10 / -5
a = -2   b = -5    -5 <  10 / -2

我不知道为什么没有人这样看待解决方案:

if (Long.MAX_VALUE/a > b) {
// overflows
}

在这两个数字中选择一个大一点的。

也许这个能帮到你:

/**
* @throws ArithmeticException on integer overflow
*/
static long multiply(long a, long b) {
double c = (double) a * b;
long d = a * b;


if ((long) c != d) {
throw new ArithmeticException("int overflow");
} else {
return d;
}
}

从 Jruby 偷来的

    long result = a * b;
if (a != 0 && result / a != b) {
// overflow
}

更新: 这段代码很短,工作得很好; 但是,对于 a = -1,b = Long.MIN _ VALUE,它失败了。

一个可能的改进是:

long result = a * b;
if( (Math.signum(a) * Math.signum(b) != Math.signum(result)) ||
(a != 0L && result / a != b)) {
// overflow
}

Note that this will catch some overflows without any division.

C/c + + (long * long) :

const int64_ w = (int64_) a * (int64_) b;
if ((long) (w >> sizeof(long) * 8) != (long) w >> (sizeof(long) * 8 - 1))
// overflow

Java (int * int,对不起,我没有在 java 中找到 int64) :

const long w = (long) a * (long) b;
int bits = 32; // int is 32bits in java
if ( (int) (w >> bits) != (int) (w >> (bits - 1))) {
// overflow
}

1. 将结果保存为大型类型(int * int 将结果放到 long 中,long * long 将结果放到 int64中)

2. cmp result > bit and result > (bits-1)

Java 库提供了安全的算术操作,可以检查长溢出/下溢。例如,如果没有溢出,Guava 的 CheckedMultiply(long a,long b)返回 ab的乘积,并且如果 a * b在有符号的 long算法中溢出,则抛出 ArithmeticException

Java8中的 int 和 long 有 Math.multiplyExactMath.addExact等等,这些在溢出时抛出未检查的 ArithmeticException

我想以约翰 · 库格曼的回答为基础,而不是直接编辑。对于他的测试用例(MIN_VALUE = -10MAX_VALUE = 10)来说,这是因为 MIN_VALUE == -MAX_VALUE的对称性,而对于两个补数整数来说则不是这样。实际上,是 MIN_VALUE == -MAX_VALUE - 1

scala> (java.lang.Integer.MIN_VALUE, java.lang.Integer.MAX_VALUE)
res0: (Int, Int) = (-2147483648,2147483647)


scala> (java.lang.Long.MIN_VALUE, java.lang.Long.MAX_VALUE)
res1: (Long, Long) = (-9223372036854775808,9223372036854775807)

当应用到真实的 MIN_VALUEMAX_VALUE时,约翰 · 库格曼的答案在 a == -1b ==任何其他情况下(点首先由凯尔提出)产生溢出情况。有个办法可以解决这个问题:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;


if ((a == -1 && b == Long.MIN_VALUE) ||
(a != -1 && a != 0 && ((b > 0 && b > maximum / a) ||
(b < 0 && b < maximum / a))))
{
// Overflow
}

对于任何 MIN_VALUEMAX_VALUE,它都不是通用的解决方案,但对于 Java 的 LongInteger以及 ab的任何值,它都是通用的。

正如已经指出的那样,Java 8具有 Math.xxxExact 方法,该方法在溢出时抛出异常。

如果您的项目不使用 Java8,您仍然可以“借用”它们相当紧凑的实现。

Here are some links to these implementations in the JDK source code repository, no guarantee whether these will stay valid but in any case you should be able to download the JDK source and see how they do their magic inside the java.lang.Math class.

Math.multiplyExact(long, long) Http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/math.java#l925

Math.addExact(long, long) Http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/math.java#l830

等等,等等。

更新: 切换到第三方网站的无效链接到 Open JDK 的 Mercurial 仓库的链接。

这是我能想到的最简单的方法

int a = 20;
long b = 30;
long c = a * b;


if(c / b == a) {
// Everything fine.....no overflow
} else {
// Overflow case, because in case of overflow "c/b" can't equal "a"
}

我没有回答,但是看看 Java 的代码,很简单。在 JDK8中,它将结果转换为长运算,并将运算结果向下转换为 int 值,然后与长运算结果进行比较,查看值是否发生了变化。下面的代码比我解释的更好。

@HotSpotIntrinsicCandidate
public static int multiplyExact(int x, int y) {
long r = (long)x * (long)y;
if ((int)r != r) {
throw new ArithmeticException("integer overflow");
}
return (int)r;
}