我应该使用什么算法进行高性能大整数除法?

时间:2015-10-05 06:27:09

标签: c++ c algorithm biginteger division

我将大整数编码为size_t数组。我已经有其他操作工作(加,减,乘);以及一位数的划分。但是如果可能的话,我想匹配我的乘法算法的时间复杂度(目前是Toom-Cook)。

我收集了线性时间算法,用于采用我的被除数的乘法逆的各种概念。这意味着我理论上可以在与乘法相同的时间复杂度中实现除法,因为无论如何,线性时间操作通过比较是“无关紧要的”。

我的问题是,我该怎么做呢?什么类型的乘法逆在实践中最好? Modulo 64^digitcount?当我将乘法逆乘以我的除数时,我可以推卸计算由于整数截断而丢弃的数据部分吗?任何人都可以提供C或C ++伪代码或准确解释如何做到这一点吗?

或者是否存在比基于逆的方法更好的专用除法算法?

编辑:我挖出了上面提到的“反向”方法。在“Art of Computer Programming,Volume 2:Seminumerical Algorithms”的第312页上,Knuth提供了“算法R”,它是一种高精度的倒数。他说它的时间复杂度小于乘法的时间复杂度。然而,将它转换为C并测试它并且不清楚将消耗多少开销内存等,直到我对其进行编码,这将花费一些时间,这是非常重要的。如果没有人打败我,我会发布它。

2 个答案:

答案 0 :(得分:4)

GMP库通常是良好算法的良好参考。他们的documented algorithms for division主要取决于选择一个非常大的基数,以便您将4位数字除以2位数字,然后通过长除法进行。

长分区需要计算2位数乘1位数的商;这可以通过递归方式完成,也可以通过预计算逆并估算商,就像使用Barrett减少一样。

2n - 位数除以n位数时,递归版本的费用为O(M(n) log(n)),其中M(n)是乘以n的费用}比特数。

如果使用牛顿算法计算逆,使用Barrett减少的版本将花费O(M(n)),但根据GMP的文档,隐藏常数要大得多,所以这个方法只适用于非常大的部门。

更详细地说,大多数除法算法背后的核心算法是"估计商和减少"计算,计算(q,r)以便

x = qy + r

但没有0 <= r < y的限制。典型的循环是

  • 估算q
  • 的商x/y
  • 计算相应的缩减r = x - qy
  • 可选择调整商,使缩减r处于某个所需的时间间隔
  • 如果r过大,请使用r代替x重复。

x/y的商将是所有q生成的总和,r的最终值将是真正的余数。

例如,教科书长期划分就是这种形式。例如第3步涵盖了您猜测的数字太大或太小的情况,并进行调整以获得正确的值。

分而治之的方法通过计算x/y估算x'/y'的商,其中x'y'x和{{1}的前导数字}}。通过调整大小可以进行优化,但如果yx'的两倍,则IIRC会获得最佳效果。

如果你坚持使用整数运算,那么乘以逆的方法是最简单的IMO。基本方法是

  • 使用y'
  • 估算y的倒数
  • 使用m = floor(2^k / y)
  • 估算x/y

事实上,实际实现可以容忍q = 2^(i+j-k) floor(floor(x / 2^i) m / 2^j)中的其他错误,如果这意味着您可以使用更快的互惠实现。

这个错误很难分析,但是如果我记得这样做的话,你想要选择mi,以便j由于错误的累积而产生,您想选择x ~ 2^(i+j)以最大限度地减少整体工作。

随后的缩减将有x / 2^i ~ m^2,因此这为选择r ~ max(x/m, y)提供了一条经验法则:您希望k的大小与您计算的商的位数有关每次迭代 - 或者相当于每次迭代要从m中删除的位数。

答案 1 :(得分:3)

我不知道乘法逆算法,但听起来像Montgomery Reduction或Barrett减少的修改。

我对bigint的分歧有点不同。

bignum division。特别是看一下近似分频器和那里的2个链路。一个是我的定点分频器,其他是快速乘法算法(如NTT上的karatsuba,Schönhage-Strassen)测量,以及我对32bit Base的快速NTT实现的链接。

我不确定逆乘法器是否正确。

它主要用于模数运算,其中分频器是常量。我担心,对于任意划分,获得bigint逆转所需的时间和操作可能比标准划分本身更大,但是因为我不熟悉它我可能是错的

我在实现中看到的最常用的分频器是Newton-Raphson分区,它与上面链接中的近似分频器非常相似。

近似/迭代分频器通常使用乘法来定义它们的速度。

对于足够小的数字,通常是长二进制除法和32/64位数字基本除法,如果不是最快的话,速度足够快:通常它们的开销很小,让n成为最大值处理(不是数字! )

二元分区示例:

O(log32(n).log2(n)) = O(log^2(n)) 它遍历所有有效位。在每次迭代中,您需要compare, sub, add, bitshift。每个操作都可以在log32(n)中完成,log2(n)是位数。

这是我的一个bigint模板(C ++)的二进制除法示例:

template <DWORD N> void uint<N>::div(uint &c,uint &d,uint a,uint b)
    {
    int i,j,sh;
    sh=0; c=DWORD(0); d=1;
    sh=a.bits()-b.bits();
    if (sh<0) sh=0; else { b<<=sh; d<<=sh; }
    for (;;)
        {
        j=geq(a,b);
        if (j)
            {
            c+=d;
            sub(a,a,b);
            if (j==2) break;
            }
        if (!sh) break;
        b>>=1; d>>=1; sh--;
        }
    d=a;
    }

N是用于存储bigint数字的32位DWORD的数量。

  • c = a / b
  • d = a % b
  • qeq(a,b)是比较:a >= b大于或等于(log32(n)=N中完成)
    它会返回0的{​​{1}},a < b的{​​{1}},1的{​​{1}}
  • a > b2

速度提升是因为这不使用乘法(如果你不计算位移)

如果你使用像2 ^ 32(ALU块)这样的大基数的数字,那么你可以使用ALU操作中的32位构建以多项式样式重写整体。
这通常比二进制长除法更快,想法是将每个DWORD处理为单个数字,或者递归地将使用过的算术除以一半,直到达到CPU能力为止。
division by half-bitwidth arithmetics

最重要的是用bignums计算

如果你已经优化了基本操作,那么复杂性可以进一步降低,因为子结果随着迭代变小(改变基本操作的复杂性)一个很好的例子就是基于NTT的乘法。

开销可能会搞砸了。

由于这个原因,运行时有时不会复制大的O复杂度,因此您应该始终测量阈值并使用更快的方法来使用位数来获得最大性能并优化您的能力。