【问题标题】:How can I check if multiplying two numbers in Java will cause an overflow?如何检查Java中的两个数字相乘是否会导致溢出?
【发布时间】:2010-12-12 02:02:41
【问题描述】:

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

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

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

更新:ab 在我的场景中总是非负数。

【问题讨论】:

标签: java math long-integer integer-overflow


【解决方案1】:

Java 8 有 Math.multiplyExactMath.addExact 等用于整数和长整数。这些会在溢出时抛出未经检查的ArithmeticException

【讨论】:

    【解决方案2】:

    如果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 总是非负数,这会在一定程度上简化这种方法。
    • 我认为这可能会失败:a = -1 和 b = 10。最大值 / a 表达式导致 Integer.MIN_VALUE 并在不存在时检测溢出
    • 这真是太好了。对于那些想知道的人,这是因为对于整数nn &gt; xn &gt; floor(x) 相同。对于正整数,除法执行隐式下限。 (对于负数,它会向上取整)
    • 要解决 a = -1b = 10 问题,请参阅下面的答案。
    【解决方案3】:

    有一些 Java 库提供安全的算术运算,用于检查长溢出/下溢。例如,Guava 的LongMath.checkedMultiply(long a, long b) 返回ab 的乘积,前提是它不会溢出,如果a * b 在有符号long 算术中溢出,则抛出ArithmeticException

    【讨论】:

    • 这是最好的答案——使用一个由真正了解 Java 机器算法的人实现的库,并且已经过很多人的测试。不要尝试自己编写或使用其他答案中发布的任何半生不熟的未经测试的代码!
    • @Enerccio -- 我不明白你的评论。你是说 Guava 不能在所有系统上工作吗?我可以向你保证,它可以在 Java 所做的任何地方工作。您是说重用代码通常是一个坏主意吗?如果是这样,我不同意。
    • @Rich 我是说包含庞大的库以便您可以使用一个函数是一个坏主意。
    • 为什么?如果您正在编写大型应用程序,例如为企业编写,那么类路径上的额外 JAR 不会造成任何伤害,而且 Guava 中有很多非常有用的代码。重用他们经过仔细测试的代码比尝试编写自己的相同版本要好得多(我认为这是您推荐的?)。如果您在一个额外的 JAR 将非常昂贵的环境中编写(在哪里?嵌入式 Java?)那么也许您应该只从 Guava 中提取这个类。从 StackOverflow 复制未经测试的答案是否比复制 Guava 仔细测试的代码更好?
    • 对于条件 if-then 可以处理的事情,抛出异常是不是有点矫枉过正?
    【解决方案4】:

    您可以改用 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()
    }
    

    【讨论】:

    • 我发现这个解决方案相当慢
    • 不过,这可能是最好的方法。我认为这是一个数值应用程序,这就是为什么我没有直接推荐它,但这确实可能是解决这个问题的最佳方法。
    • 我不确定您是否可以将“>”运算符与 BigInteger 一起使用。应该使用 compareTo 方法。
    • 更改为 compareTo,速度可能很重要,也可能无关紧要,取决于使用代码的环境。
    【解决方案5】:

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

    【讨论】:

    • 你的意思是:ceil(log(a)) + ceil(log(b)) &gt; log(Long.MAX)?
    • 我查过了。对于较小的值,它比 BigInteger 快 20%,对于接近 MAX 的值,它几乎相同(快 5%)。 Yossarian 的代码是最快的(比 BigInteger 快 95% 和 75%)。
    • 我怀疑它在某些情况下可能会失败。
    • 请记住,整数日志实际上只是计算前导零的数量,您可以优化一些常见情况(例如 if ((a | b) & 0xffffffff00000000L) == 0),您知道自己是安全的)。另一方面,除非您可以针对最常见的情况将优化降低到 30/40-ish 时钟周期,否则 John Kugelman 的方法可能会执行得更好(我记得整数除法是 c.2 位/时钟周期)。
    • P.S.抱歉,我想我需要在 AND 掩码中设置一个额外的位 (0xffffffff80000000L)——这有点晚了,但你明白了......
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

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

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

      【讨论】:

      • 我没有投反对票,但如果 aLong.MIN_VALUEMath.Abs(a) 不起作用。
      • @John - a 和 b > 0。我认为 Yossarian 的方法 (b != 0 && a > Long.MAX_VALUE / b) 是最好的。
      • @Thomas, a 和 b >= 0,即非负数。
      • 对,但在这种情况下不需要 Abs。如果允许负数,则至少在一种边缘情况下会失败。这就是我要说的,只是吹毛求疵。
      • 在 Java 中你必须使用 Math.abs,而不是 Math.Abs​​(C# 家伙?)
      【解决方案8】:

      从 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
      【解决方案9】:

      正如已经指出的,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

      等等等等

      更新:将指向第 3 方网站的无效链接切换到指向 Open JDK 的 Mercurial 存储库的链接。

      【讨论】:

        【解决方案10】:

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

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

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

        【讨论】:

        • a 是大还是小我觉得没关系?
        【解决方案11】:

        我想以 John Kugelman 的答案为基础,而不是通过直接编辑来替换它。由于MIN_VALUE == -MAX_VALUE 的对称性,它适用于他的测试用例(MIN_VALUE = -10MAX_VALUE = 10),而二进制补码整数并非如此。实际上,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 时,John Kugelman 的答案会在 a == -1b == 出现其他任何情况时产生溢出情况(Kyle 首先提出的观点)。解决方法如下:

        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,而不是任何其他情况(包括您的示例测试用例)。我必须改变很多。
        • 出于原发帖者需要以外的原因;对于像我这样发现此页面的人,因为他们需要针对更一般情况的解决方案(处理负数而不是严格的 Java 8)。事实上,由于该解决方案不涉及纯算术和逻辑之外的任何功能,因此它也可以用于 C 或其他语言。
        【解决方案12】:

        也许:

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

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

        编辑:添加 b != 0。

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

        【讨论】:

        • 溢出导致完美循环时也会失败。
        • 你有一个例子说明你的意思吗?
        • 刚刚写了一个小测试:使用 BigInteger 比使用这种除法方法慢 6 倍。所以我认为在性能方面对极端情况进行额外检查是值得的。
        • 不太了解 java 编译器,但在许多其他情况下,a * b / b 这样的表达式可能会被优化为 a
        • TokenMacGuy - 如果有溢出的危险,就不能这样优化。
        【解决方案13】:

        也许这会对你有所帮助:

        /**
         * @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,因此无济于事。
        • 你测试过这个吗?对于 a & b 的任何大但未溢出的值,这将由于乘法的双精度版本中的舍入错误而失败(尝试例如 123456789123L 和 74709314L)。如果你不懂机器算术,猜测这种精确问题的答案比不回答更糟糕,因为它会误导人们。
        【解决方案14】:

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

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

        【讨论】:

          【解决方案15】:

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

          【讨论】:

            猜你喜欢
            • 2021-06-21
            • 1970-01-01
            • 1970-01-01
            • 2020-05-05
            • 2011-02-12
            • 2012-11-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多