使用乘法执行整数除法

use*_*750 15 optimization assembly bit-manipulation multiplication division

看一下编译器生成的x86程序集,我注意到(无符号)整数除法有时实现为整数乘法.这些优化似乎遵循这种形式

value / n => (value * ((0xFFFFFFFF / n) + 1)) / 0x100000000
Run Code Online (Sandbox Code Playgroud)

例如,执行除以9:

12345678 / 9 = (12345678 * 0x1C71C71D) / 0x100000000
Run Code Online (Sandbox Code Playgroud)

除以3将使用乘法0x55555555 + 1,依此类推.

利用mul指令将结果的高部分存储在edx寄存器中的事实,可以使用具有魔术值的单个乘法来获得除法的最终结果.(虽然这种优化有时与最后的逐位移位一起使用.)

我想了解一下这实际上是如何运作的.这种方法什么时候有效?为什么必须将1添加到我们的"神奇数字"中?

Mys*_*ial 19

该方法称为"由不变乘法划分".

你所看到的常数实际上是倒数的近似值.

所以而不是计算:

N / D = Q
Run Code Online (Sandbox Code Playgroud)

你做了这样的事情:

N * (1/D) = Q
Run Code Online (Sandbox Code Playgroud)

哪里1/D是可以预先计算的倒数.

从根本上说,除非D是二次幂,否则倒数是不精确的.因此会涉及一些舍入错误.在+1你看到的是有纠正的舍入误差.


最常见的例子是除以3:

N / 3 = (N * 0xaaaaaaab) >> 33
Run Code Online (Sandbox Code Playgroud)

哪里0xaaaaaaab = 2^33 / 3 + 1.

这种方法将推广到其他除数.

  • 规范性参考文献是:T.Granlund和PL Montgomery,"使用乘法划分不变整数",参见SIGPLAN '94编程语言设计与实现会议论文集,1994年,第61-72页. (5认同)
  • 另外,更近期的参考文献:N.Möller和T. Granlund,"通过不变整数改进除法",*IEEE Transactions on Computers*,vol.60,不.2,pp.165 -175,2011年2月. (3认同)