使用移位和加/减来除以常数

时间:2011-02-03 12:42:48

标签: performance assembly bit-manipulation math approximation

大家好我试图用无符号常数除以仅使用移位和加/减 - 如果它是乘法我没有问题,但是我有点被分裂所困扰。

例如,假设常数除数为192,可以说红利为8000

“完整结果”y = 8000/192 = 41(假设我没有保留小数位)

y = 8000>> 8 ... 31 y = 8000>> 7 ... 62

但我如何获得更准确的解决方案?

非常感谢!

3 个答案:

答案 0 :(得分:9)

几乎可以肯定有一种更优雅的方式,但这足以让你开始。

通常以这种方式划分是通过乘以倒数来完成的,即先乘以然后右移。

(注意:请记住,乘法可以通过移位和添加来完成(例如n * 3 = (n*2) + (n*1) = (n << 1) + (n) )但我只是在这里使用乘法。你的问题说“移位和补充”,我正在证明我的速记使用乘法)

在下面的例子中,我试图用一个例子来解释这些概念。在您的具体情况下,您需要考虑诸如

之类的问题
  1. 签名(我在下面使用无符号整数)

  2. 溢出(下面我使用的是32位无符号长整数来保存中间值,但是如果你在较小的uC上小心,请相应调整

  3. 舍入(例如,应该9/5返回1或2?在C中,它是1,但也许你想要2,因为它更接近正确答案?)

  4. 在您可以(可用位)的范围内,在分割之前完成所有乘法(最小化截断错误)。再次注意溢出。

  5. 正如我所说,请阅读以下内容以了解概念,然后根据您的需求进行定制。

    除以192与乘以1/192相同,与除以(64 * 3)相同。没有1/3的精确(有限)二进制表示,所以我们用0x5555 /(1 <&lt; 16)近似它。

    除以192,我们除以64然后除以3.除以3,我们乘以0x5555并向右移16(或乘以0x55和&gt;&gt; 8,或......)< / p>

    //                8000/192          =
    //                ((8000/64)/3)     =
    //                ((8000 >> 6) / 3) =
    //                (((8000 >> 6) * 0x5555) >> 16)
    //                (((8000 * 0x5555) >> 22
    

    请注意括号是故意的。您想要计算(8000 * (0x5555/(1 << 16)),因为第二项为0,产品为0.不好。

    因此代码中的单行代码将类似于:

     printf("Answer:  %lu\n", ((8000UL * 0x5555UL) >> 22));
    

    这将产生41,这是{C}将为8000/192产生的,即使42“更接近”。通过检查LSB,您可以根据需要进行舍入。

    可以写一篇关于这个主题的论文,但幸运的是 someone much smarter than me already has

答案 1 :(得分:4)

我开发了一个恒定除法生成器,可以通过任何常量轻松为您提供优化的除法。它遵循“Hacker's Delight”的想法。

名为“kdiv”的工具可在sourceforge上找到:

http://sourceforge.net/projects/kdiv/

答案 2 :(得分:2)

对于这类事情我会看黑客喜悦的书。我没有我的副本,但是如果你看看你的除数192,那就是0xC0,那么你可以将顶部和底部除以0x40,移位8000&gt;&gt; 6 = 125. 8000/192 - &gt; 125/3,但是你必须将它除以3.我们知道答案将介于125/2和125/4之间。使用这些特定数字125是0x7d或b1111101,它是b100000 + 11101的3倍,是(3倍0x20)+(3倍8)+ 5所以125/3 = 0x20 + 0x8 +(5/3)和5/3是快速确定为大于1但小于2,因此0x28 + 1 = 41.如果除数位模式一直显示在分子位模式的高位,则仅继续减少。我不知道黑客喜欢什么或其他类似的消息来源对此事说,我只是碰巧注意到这些特定数字的模式。