用c语言大数乘法大数除法,关于c ++:高性能大整数除法应使用哪种算法?

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

我收集了一些线性时间算法,用于采用各种除法乘数逆的概念。这意味着从理论上讲,我可以以与乘法相同的时间复杂度来实现除法,因为无论如何,线性时间运算都是"无关紧要的"。

我的问题是,我实际上该怎么做?在实践中哪种类型的乘法逆是最好的?模数64^digitcount?当我将除数乘以除数时,是否可以回避计算由于整数截断而被丢弃的数据部分?任何人都可以提供C或C ++伪代码,或给出具体的解释方法吗?

还是有专门的除法算法甚至比基于逆的方法更好?

编辑:我挖了我正在得到上面提到的"反向"方法。在"计算机编程艺术,第2卷:半数值算法"的第312页上,Knuth提供了" Algorithm R",它是一种高精度的倒数。他说,它的时间复杂度小于乘法。但是,将其转换为C并进行测试并不容易,并且不清楚在我编写此代码之前会消耗多少开销内存等,这将需要一段时间。如果没有人击败我,我会发布它。

您知道这些方法的渐近复杂性吗? 就传递给函数的位数而言? 与桌面乘法的O(n ^ 2)等进行比较

O(n*log(n))听起来太快,那比最快的乘法快。 我怀疑由于某些原因它会变慢,但是如果我能找出原因,我会尽快与您联系。

移动评论来回答,添加了一些信息的二进制长除法示例...

对于好的算法,GMP库通常是一个很好的参考。他们记录的除法算法主要取决于选择一个非常大的基数,以便将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的限制。典型的循环是

估计x/y的商q

计算相应的减少量r = x - qy

(可选)调整商,以使降低值r处于所需的间隔内

如果r太大,则用r代替x重复。

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

例如,教科书的长除法就是这种形式。例如第3步涵盖了您猜测的数字太大或太小,然后对其进行调整以获取正确值的情况。

分治法通过计算x'/y'估算x/y的商,其中x'和y'是x和y的前两位。通过调整大小可以优化的空间很大,但是如果x'是y'的两倍,则IIRC可获得最佳结果。

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

用m = floor(2^k / y)估计y的逆

用q = 2^(i+j-k) floor(floor(x / 2^i) m / 2^j)估计x/y

实际上,如果实际的实现意味着您可以使用更快的对等实现,则可以容忍m中的其他错误。

错误是很难分析的,但是如果我回想起该错误的方法,则希望选择i和j,以便使x ~ 2^(i+j)由于错误的累积方式而异,并且希望选择x / 2^i ~ m^2以最小化整体工作。

随后的减少将为r ~ max(x/m, y),因此给出了选择k的经验法则:您希望m的大小约为每次迭代计算的商的位数,或者等效为您的位数希望每次迭代从x中删除。

我想知道他们是拒绝了Knuths的建议,还是只是不知道...我要花点时间来决定。

@VoidStar您应该尝试写信给图书馆的作者并询问;如果您幸运的话,他们可能愿意讨论这个问题。

谢谢,我给他们发送了有关gmp-discuss的电子邮件。

@VoidStar:尽管我没有方便的Knuth,但我相信算法R只是用于计算逆数的牛顿算法,它是您要用来进行Barrett约简的预计算步骤的方法。

@Hurkyl:那么Barrett约简只是利用反演的一种方法?为什么不简单地乘以它呢?如果您有一个真正的逆数可以乘以得到答案,那么我看不出Barrett约简的意义是什么。尽管在这种情况下我仍然不清楚Barrett约简,但是其定义表明它是针对模块化算术的(Im在没有模数的情况下进行纯整数除法)。

@VoidStar:是的。最主要的是,我发现这些事情更容易处理,并且可以分析您是否到处都坚持使用整数(例如,在某种程度上计算floor(2^kd)而不是1d)。同样,即使您只需要商,也仍然需要计算归约率,因为累积误差意味着您所计算的商可以偏离真实商的1或2。 (此外,长除法的中间步骤还要求您计算出减少量)

谢谢,这很有意义。尽管在Knuth算法的情况下,他的算法将逆计算的精度提高到足以乘以得到商的精确度……他提供了这种情况的证明。主要问题是关于巴雷特减少时间的复杂性...它可以与通过逆w / schonhage-strassen从Knuths除法得到的O(n logn loglogn)相比较吗?还是较小的输入量会发光?

是;您需要非常精确才能做到这一点。要计算2n x n商,我认为您需要在逆线上具有2n位的精度,然后必须计算完整的2n x 2n乘法才能获得商。这比计算反精度的n位,2n x n乘以估计商然后使用n x n乘积来计算缩减量要贵得多。这相当于6 M(n)与4.5 M(n)之类的东西。

如果您在反面上计算n2位精度并执行两步降阶(即长除法),则需要两个n x n2乘积来估计商,而需要两个n x n2乘积来计算约数,结果得出< x14>。 (当您已经知道产品的一部分时,这些成本都没有考虑降低成本的技巧)(这些数字假设在乘法运算的FFT范围内,因此M(2n)的成本与< x16>,并且您可以将2nxn与成本M(1.5 n)相乘)

嗯,当我进一步看待这个倒数时,您需要在正常的" FFT范围"大小的100倍范围内(又称可笑的巨大),然后才能真正实现复杂度的提高(涉及很多重复乘法)。我仍然在努力找出Barrett的确切时间复杂度(我假设它在nlogn和n^2之间),但是我敢打赌它在实践中确实能更好地工作。现在,我只需要弄清楚它的C代码。

@VoidStar:您必须仅使用所需的精度而不是全精度来仔细地执行每个步骤-逆计算的每个步骤都以上一步的精度两倍进行。如果操作正确,则整个计算的成本应仅为最后一步的两倍。

@Hurkyl:没错,那正是Knuths写作所描绘的,只是它仍然比整体乘法慢许多倍,包括在末尾进行互操作以获得余数。另外...不幸的是,此处的Barret约简算法假设:除an时,a必须小于n^2。那就复杂了...

我不知道乘法逆算法,但听起来像蒙哥马利约简或巴雷特约简的修改。

我对bigint的划分有些不同。

参见bignum部门。特别要看一下近似除法器和那里的2个链接。一个是我的定点除法器,另一个是快速乘法算法(例如karatsuba,NTT上的Sch?nhage-Strassen),带有测量值,并且是我针对32位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 void uint::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中完成)

对于a < b返回0,对于a > b返回1,对于a == b返回2

sub(c,a,b)是c = a - b

通过不使用乘法来提高速度(如果不计算位移)

如果您使用具有大底数的数字(例如2 ^ 32(ALU块)),则可以使用32位内置ALU操作以多项式的形式重写整个整数。

这通常比二进制长除法还要快,其想法是将每个DWORD视为一个数字,或者将使用的算术递归除以一半,直到达到CPU性能。

参见除以半位宽算术

最重要的是,使用bignums进行计算

如果您优化了基本运算,那么随着子结果随着迭代而变小(更改基本运算的复杂度),复杂度会进一步降低(一个很好的例子是基于NTT的乘法)。

开销可能使事情变得混乱。

因此,运行时有时不会复制大的O复杂性,因此您应始终测量阈值,并对使用的位计数使用更快的方法,以获取最佳性能并优化您的性能。

使用Big O表示法时,应始终去除标量常量。 O(log32(n)) = O(log(N)),因为它们与描述增长率无关。其次,Big O最有用,并且最常用的词法是输入中的位数。因此,数字计数是您应该以此为基础的,而不是可以处理的值的大小。您所展示的是一个O(n^2)算法,该算法是可以通过的,但将Knuths高速倒数与快速乘法相结合,则可能更快(使用可笑的大输入。您的输入非常适合中型对象)。

@VoidStar在tat情况下,结果在O(n^2)中用于二进制长除法

@VoidStar出于好奇,"荒谬的大"和"中等的"是什么意思?几位数?

@FabioTurati取决于实现方式...例如,请参见快速bignum sqr基于NTT的矿难实现阈值是操作数的310*32=9920位(结果的19840位)和NTT mul具有1396 * 32 = 44672结果的确是非常庞大的。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

你可能感兴趣的:(用c语言大数乘法大数除法)