大家好我试图用无符号常数除以仅使用移位和加/减 - 如果它是乘法我没有问题,但是我有点被分裂所困扰。
例如,假设常数除数为192,可以说红利为8000
“完整结果”y = 8000/192 = 41(假设我没有保留小数位)
y = 8000>> 8 ... 31 y = 8000>> 7 ... 62
但我如何获得更准确的解决方案?
非常感谢!
答案 0 :(得分:9)
几乎可以肯定有一种更优雅的方式,但这足以让你开始。
通常以这种方式划分是通过乘以倒数来完成的,即先乘以然后右移。
(注意:请记住,乘法可以通过移位和添加来完成(例如n * 3 = (n*2) + (n*1) = (n << 1) + (n) )
但我只是在这里使用乘法。你的问题说“移位和补充”,我正在证明我的速记使用乘法)
在下面的例子中,我试图用一个例子来解释这些概念。在您的具体情况下,您需要考虑诸如
之类的问题签名(我在下面使用无符号整数)
溢出(下面我使用的是32位无符号长整数来保存中间值,但是如果你在较小的uC上小心,请相应调整
舍入(例如,应该9/5返回1或2?在C中,它是1,但也许你想要2,因为它更接近正确答案?)
在您可以(可用位)的范围内,在分割之前完成所有乘法(最小化截断错误)。再次注意溢出。
正如我所说,请阅读以下内容以了解概念,然后根据您的需求进行定制。
除以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上找到:
答案 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.如果除数位模式一直显示在分子位模式的高位,则仅继续减少。我不知道黑客喜欢什么或其他类似的消息来源对此事说,我只是碰巧注意到这些特定数字的模式。