java - (a * b)/c MulDiv 和处理中间乘法溢出

标签 java algorithm long-integer division

我需要做以下算术:

long a,b,c;
long result = a*b/c;

虽然结果保证适合 long,但乘法不是,所以它可能会溢出。

我试着一步一步地做(先乘后除),同时通过将 a*b 的中间结果拆分成一个最大为 4 的 int 数组( much就像 BigInteger 正在使用它的 int[] mag 变量一样)。

在这里,我被部门困住了。我无法理解进行精确除法所需的位移位。我只需要商(不需要余数)。

假设的方法是:

public static long divide(int[] dividend, long divisor)

此外,我不考虑使用 BigInteger,因为这部分代码需要快速(我想坚持使用基元和基元数组)。

如有任何帮助,我们将不胜感激!

编辑: 我并不想自己实现整个 BigInteger。我想做的是比使用通用 BigInteger< 更快地解决特定问题(a*b/c,其中 a*b 可能溢出)/.

Edit2:如果能以一种聪明的方式完成,那将是理想的,完全不会溢出,评论中出现了一些提示,但我仍在寻找正确的.

更新: 我尝试将 BigInteger 代码移植到我的特定需求,而不创建对象,并且在第一次迭代中,与使用 BigInteger(在我的开发电脑上)相比,我的速度提高了约 46%。

然后我尝试了一些修改过的@David Eisenstat 解决方案,这让我减少了 ~56%(我从 Long.MIN_VALUELong.MAX_VALUE 运行了 100_000_000_000 个随机输入)与 BigInteger 相比,运行时间(超过 2 倍)(与我采用的 BigInteger 算法相比,大约是 18%)。

还会有更多的优化和测试迭代,但在这一点上,我认为我必须接受这个答案是最好的。

最佳答案

我一直在修补一种方法,即 (1) 将 ab 与 21 位肢体上的学校算法相乘 (2) 进行长除法通过 c,残差 a*b - c*q 的不寻常表示使用 double 来存储高阶位和一个 long 来存储低位。我不知道它是否可以与标准长除法竞争,但为了您的享受,

public class MulDiv {
  public static void main(String[] args) {
    java.util.Random r = new java.util.Random();
    for (long i = 0; true; i++) {
      if (i % 1000000 == 0) {
        System.err.println(i);
      }
      long a = r.nextLong() >> (r.nextInt(8) * 8);
      long b = r.nextLong() >> (r.nextInt(8) * 8);
      long c = r.nextLong() >> (r.nextInt(8) * 8);
      if (c == 0) {
        continue;
      }
      long x = mulDiv(a, b, c);
      java.math.BigInteger aa = java.math.BigInteger.valueOf(a);
      java.math.BigInteger bb = java.math.BigInteger.valueOf(b);
      java.math.BigInteger cc = java.math.BigInteger.valueOf(c);
      java.math.BigInteger xx = aa.multiply(bb).divide(cc);
      if (java.math.BigInteger.valueOf(xx.longValue()).equals(xx) && x != xx.longValue()) {
        System.out.printf("a=%d b=%d c=%d: %d != %s\n", a, b, c, x, xx);
      }
    }
  }

  // Returns truncate(a b/c), subject to the precondition that the result is
  // defined and can be represented as a long.
  private static long mulDiv(long a, long b, long c) {
    // Decompose a.
    long a2 = a >> 42;
    long a10 = a - (a2 << 42);
    long a1 = a10 >> 21;
    long a0 = a10 - (a1 << 21);
    assert a == (((a2 << 21) + a1) << 21) + a0;
    // Decompose b.
    long b2 = b >> 42;
    long b10 = b - (b2 << 42);
    long b1 = b10 >> 21;
    long b0 = b10 - (b1 << 21);
    assert b == (((b2 << 21) + b1) << 21) + b0;
    // Compute a b.
    long ab4 = a2 * b2;
    long ab3 = a2 * b1 + a1 * b2;
    long ab2 = a2 * b0 + a1 * b1 + a0 * b2;
    long ab1 = a1 * b0 + a0 * b1;
    long ab0 = a0 * b0;
    // Compute a b/c.
    DivBy d = new DivBy(c);
    d.shift21Add(ab4);
    d.shift21Add(ab3);
    d.shift21Add(ab2);
    d.shift21Add(ab1);
    d.shift21Add(ab0);
    return d.getQuotient();
  }
}

public strictfp class DivBy {
  // Initializes n <- 0.
  public DivBy(long d) {
    di = d;
    df = (double) d;
    oneOverD = 1.0 / df;
  }

  // Updates n <- 2^21 n + i. Assumes |i| <= 3 (2^42).
  public void shift21Add(long i) {
    // Update the quotient and remainder.
    q <<= 21;
    ri = (ri << 21) + i;
    rf = rf * (double) (1 << 21) + (double) i;
    reduce();
  }

  // Returns truncate(n/d).
  public long getQuotient() {
    while (rf != (double) ri) {
      reduce();
    }
    // Round toward zero.
    if (q > 0) {
      if ((di > 0 && ri < 0) || (di < 0 && ri > 0)) {
        return q - 1;
      }
    } else if (q < 0) {
      if ((di > 0 && ri > 0) || (di < 0 && ri < 0)) {
        return q + 1;
      }
    }
    return q;
  }

  private void reduce() {
    // x is approximately r/d.
    long x = Math.round(rf * oneOverD);
    q += x;
    ri -= di * x;
    rf = repairLowOrderBits(rf - df * (double) x, ri);
  }

  private static double repairLowOrderBits(double f, long i) {
    int e = Math.getExponent(f);
    if (e < 64) {
      return (double) i;
    }
    long rawBits = Double.doubleToRawLongBits(f);
    long lowOrderBits = (rawBits >> 63) ^ (rawBits << (e - 52));
    return f + (double) (i - lowOrderBits);
  }

  private final long di;
  private final double df;
  private final double oneOverD;
  private long q = 0;
  private long ri = 0;
  private double rf = 0;
}

关于java - (a * b)/c MulDiv 和处理中间乘法溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54232987/

相关文章:

java - 使用加密数据创建自定义文件格式

c - 我的 QuickSort 代码有时会运行,但有时不会,我不知道错误发生在哪里

c# - 图像格式转换异常缓慢

c# - C# 中的 Long 与 Java 中的 Long 不具有相同的值

ios - 如何在IOS中从sqlite3_stmt sqlite3获取long long值?

PHP 字符串到 "unsigned long"(来自 C++)

java - 我应该在 Spring 5 项目中使用具有实体关系的 MongoDB 来实现端到端非阻塞吗?

java - hibernate:保存没有外键的实体时出现异常

java - 错误将字符串转换为日期 : ClassCastException

c# - 从长度为 M 的未排序数组中搜索前 N 个已排序整数?