整数除法算法

时间:2011-02-23 21:28:03

标签: c++ algorithm performance integer-division bigint

3 个答案:

答案 0 :(得分:12)

您的算法是基础10算法的变体,称为“输出9”。你的例子是使用基数1000并“逐出”999(比基数少一个)。这曾经在小学教过,作为快速检查手工计算的方法。我有一个高中数学老师,他很惊讶地发现它不再被教导,并且让我们充满了它。

在基数1000中输出999将不能作为一般除法算法。它将生成与实际商和余数一致的模999的值 - 而不是实际值。你的算法有点不同,我没有检查它是否有效,但它是基于有效地使用基数1000和除数比基数小1。如果您想尝试将其除以47,则必须先转换为基数为48的数字系统。

谷歌“淘汰了9”以获取更多信息。

编辑:我最初读的帖子太快了,你知道这是一个有效的算法。由于@Ninefingers和@Karl Bielefeldt在他们的评论中已经比我更清楚地表明,你在绩效评估中没有包括的是转换成适合当前特定除数的基数。

答案 1 :(得分:5)

答案 2 :(得分:0)

如果你需要频繁地除以相同的除数,使用(或它的幂)作为你的基数使得除法变得便宜,因为位移是基数为2的二进制整数。

如果你愿意,你可以使用base 999;使用10次幂基数没有什么特别之处,只是它使转换为十进制整数非常便宜。 (你可以一次处理一个肢体而不必对整个整数进行完全除法。这就像将二进制整数转换为十进制与将每4位转换为十六进制数字之间的区别一样。二进制 - >十六进制可以从最高有效位开始,但是转换为非幂2的基数必须是LSB优先使用除法。)

例如,要计算具有性能要求的代码高尔夫问题的Fibonacci(10 9 )的前1000个十进制数字,my 105 bytes of x86 machine code answer使用与{{3}相同的算法}}:通常的a+=b; b+=a斐波那契迭代,但每当a变得太大时,除以(幂)10。

Fibonacci比进位传播增长更快,因此有时丢弃低十进制数字并不能长期改变高位数。 (你可以保留一些额外的精度)。

除以2 的幂并不工作,除非你跟踪你丢弃了多少2的幂,因为最终的二进制 - >最后的十进制转换将取决于那个。

因此,对于这个算法,你必须进行扩展精度加法,除以10(或你想要的任何10的幂)。

我将base-10 9 肢体存储在32位整数元素中。除以10 9 是非常便宜的:只是一个指针增量跳过低肢。我没有实际执行memmove,而只是偏移下一次添加迭代使用的指针。

我认为除了10 ^ 9之外的10次幂除法会有些便宜,但是需要对每个肢体进行实际划分,并将其余部分传播到下一个肢体。

扩展精度加法比使用二进制肢体更昂贵,因为我必须通过比较手动生成结转:sum[i] = a[i] + b[i]; carry = sum < a;(无符号比较)。并且还使用条件移动指令基于该比较手动换行到10 ^ 9。但我能够将该结转用作adc的输入(x86 add-with-carry指令)。

你不需要一个完整的模数来处理额外的包装,因为你知道你最多包裹一次。

这会浪费每个32位肢体的2位:10 ^ 9而不是2^32 = 4.29... * 10^9。每个字节存储一个基数为10的数字将显着降低空间效率,并且非常性能更差,因为8位二进制加法的成本与现代64位的64位二进制加法相同-bit CPU。

我的目标是代码大小:对于纯粹的性能,我会使用64位肢体保持基数为10 ^ 19&#34;数字&#34;。 (2^64 = 1.84... * 10^19,因此每64位浪费少于1位。)这使您可以使用每个硬件add指令完成两倍的工作。嗯,实际上这可能是个问题:两个肢体的总和可能会包裹64位整数,所以只检查> 10^19就不够了。您可以在基础5*10^18或基础10^18中工作,或执行更复杂的结转检测,以检查二进制进位和手动进位。

存储打包的BCD,每4位半字节一位数,性能会更差,因为没有硬件支持阻止从一个半字节到一个字节内的下一个字节进位。

总的来说,我的版本比同一硬件上的Python扩展精度版本快了大约10倍(但它有更大的速度优化空间,通过更少的划分)。 (70秒或80秒对12分钟)

仍然,我认为对于 算法的这个特定实现(我只需要添加和除法,并且在每几次添加后发生除法),基数-10 ^ 9肢体的选择非常好。对于Nth Fibonacci数字,有更高效的算法不需要进行10亿次扩展精度加法。