如何检查Java中的两个数字是否会导致溢出?


101

我想处理将两个数字相乘会导致溢出的特殊情况。代码看起来像这样:

int a = 20;
long b = 30;

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

那是一个简化的版本。在实际程序中,a并且b在运行时从其他地方获取。我想要实现的是这样的:

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

您如何建议我最好编写此代码?

更新:ab总是非负在我的情况。


6
这太糟糕了,Java不提供对CPU的间接访问溢出标志,因为在C#实现
德鲁·诺阿克斯

Answers:


92

Java的8有Math.multiplyExactMath.addExact等,为整数且长。这些会引发未经检查ArithmeticException的溢出。


59

如果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

我应该提到,在我的情况下,a和b始终为非负数,这将在某种程度上简化此方法。
史蒂夫·麦克劳德

3
我认为这可能会失败:a = -1和b =10。最大/一个表达式在Integer.MIN_VALUE中产生,并在不存在时检测到溢出
Kyle 2014年

真的很好 对于那些想知道的人,这样做的原因是对于integer nn > x与相同n > floor(x)。对于正整数除法,会进行隐式底限运算。(对于负数则取

解决a = -1b = 10问题,请参见下面的答案。
Jim Pivarski

17

有一些Java库提供安全的算术运算,可以检查长时间的上溢/下溢。例如番石榴 LongMath.checkedMultiply(long a,long b)返回aand 的乘积b,前提是它不溢出,ArithmeticException如果a * b有符号long算术溢出则抛出该结果。


4
这是最好的答案-使用由真正了解Java机器算法的人实现的库,并且该库已经过很多人的测试。不要尝试自己编写或使用其他答案中未完成的任何未经测试的代码!
丰富

@Enerccio-我不明白你的评论。您是说Guava不适用于所有系统吗?我可以向您保证,它将与Java一样适用。您是说重用代码通常是个坏主意吗?我不同意。
丰富

2
@Rich我是说包含巨大的库,所以您可以使用一个函数是一个坏主意。
Enerccio

为什么?如果您正在编写大型应用程序(例如,为企业编写的应用程序),则类路径上的额外JAR不会造成任何危害,并且Guava中包含许多非常有用的代码。重用经过仔细测试的代码比尝试编写自己的相同版本要好得多(我认为这是您的建议?)。如果您在一个额外的JAR会非常昂贵(在哪里?嵌入式Java?)的环境中进行编写,那么也许应该从Guava中提取此类。从StackOverflow复制未经测试的答案是否比复制Guava经过仔细测试的代码更好?
丰富

对于有条件的if-then可以处理的事情,抛出异常是否有点矫kill过正?
受害者

6

您可以改用java.math.BigInteger并检查结果的大小(尚未测试代码):

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()
}

7
我相当缓慢发现该解决方案
抛出异常

不过,这可能是最好的方法。我以为这是一个数值应用程序,这就是为什么我不推荐它的原因,但这实际上可能是解决此问题的最佳方法。
Stefan Kendall,2009年

2
我不确定您可以在BigInteger中使用'>'运算符。应该使用compareTo方法。
Pierre

更改为compareTo,速度可能重要与否,取决于使用代码的情况。
Ulf Lindback,09年

5

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


你的意思是:ceil(log(a)) + ceil(log(b)) > log(Long.MAX)
Thomas Jung

1
我检查了 对于较小的值,它比BigInteger快20%,对于接近MAX的值几乎相同(快5%)。Yossarian的代码是最快的(比BigInteger快95%和75%)。
Thomas Jung

我宁可怀疑它在某些情况下可能会失败。
Tom Hawtin-抢险活动

请记住,整数日志实际上只是在计算前导零的数目,并且您可以优化一些常见情况(例如,如果您知道自己很安全,则如果(((a | b)&0xffffffff00000000L)== 0))。另一方面,除非在最常见的情况下将优化降低到30/40 ish时钟周期,否则约翰·库格曼(John Kugelman)的方法可能会执行得更好(我记得,整数除法为c。2位/时钟周期)。
尼尔·科菲

PS Sorr,我想我需要在AND掩码中设置一个额外的位(0xffffffff80000000L)-有点晚了,但您知道了...
Neil Coffey

4

Java是否有类似int.MaxValue的东西?如果是,请尝试

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

编辑:看到有问题的Long.MAX_VALUE


我并没有投票,Math.Abs(a)如果a是的话我不会工作Long.MIN_VALUE
约翰·库格曼

@John-a和b>0。我认为Yossarian的方法(b!= 0 && a> Long.MAX_VALUE / b)是最好的。
Thomas Jung

@ Thomas,a和b> = 0,即非负数。
史蒂夫·麦克劳德

2
是的,但在这种情况下,不需要腹肌。如果允许使用负数,则至少在一种边缘情况下将失败。我只是说挑剔而已。
约翰·库格曼

在Java中,您必须使用Math.abs,而不是Math.Abs​​(C#家伙?)
dfa

4

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

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"
}

3

从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
}

请注意,这将捕获一些溢出而不进行任何除法。


您可以使用Long.signum代替Math.signum
退出是因为SE为EVIL

3

如前所述,Java 8具有Math.xxxExact方法,这些方法会在溢出时引发异常。

如果您的项目没有使用Java 8,您仍然可以“借用”它们的紧凑的实现。

这是JDK源代码存储库中这些实现的一些链接,不能保证它们是否仍然有效,但是无论如何您都应该能够下载JDK源代码,并查看它们如何在java.lang.Math类中发挥作用。

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存储库。


2

我不确定为什么没人在看这样的解决方案:

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

选择一个较大的两个数字。


2
我认为a是大还是小无关紧要?
托马斯·阿勒

2

我想以约翰·库格曼(John Kugelman)的答案为基础,而不用直接编辑它来代替它。它适用于他的测试案例(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约翰Kugelman的回答时,产生溢出的情况下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


我只是认为这样做会不必要地使其复杂化,因为该解决方案仅在时有效MIN_VALUE = -MAX_VALUE - 1,而在任何其他情况下(包括您的示例测试用例)都不会起作用。我必须改变很多。
Jim Pivarski

1
由于超出原始海报需求的原因;对于像我这样的人,他们找到了此页面是因为他们需要一种更通用的解决方案(处理负数而不是严格的Java 8)。实际上,由于此解决方案不涉及除纯算术和逻辑之外的任何函数,因此它也可以用于C或其他语言。
吉姆·皮瓦尔斯基

1

也许:

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

不确定此“解决方案”。

编辑:添加b!= 0。

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


当溢出导致完美循环时也会失败。
Stefan Kendall

你有你的意思的例子吗?
Thomas Jung,

刚刚写了一点测试:使用BigInteger比使用这种除法慢6倍。因此,我认为对极端情况进行额外检查在性能方面是值得的。
mhaller

对Java编译器了解不多,但类似的表达式a * b / b可能会a在许多其他情况下进行优化。
SingleNegationElimination

TokenMacGuy-如果存在溢出危险,则无法像这样进行优化。
Tom Hawtin-抢险活动

1

也许这会帮助您:

/**
 * @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;
    }
}

没有帮助作为操作数之一long
Tom Hawtin-抢险

1
你甚至测试过吗?对于任何较大但不溢出的a&b值,由于乘法的双精度版本中的舍入误差(例如123456789123L和74709314L),该操作将失败。如果您不懂机器运算,那么猜测这种精确问题的答案比不回答要糟糕,因为它会误导人们。
丰富

-1

c / c ++(长*长):

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结果>>位和结果>>(位-1)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.