我正在研究一种方法,只使用二元运算符(<<>> + ^〜& |!)将有符号整数除以2的幂,结果必须是朝向我在Stackoverflow上遇到问题的this question,然而,我无法理解它为什么会起作用。这是解决方案:
int divideByPowerOf2(int x, int n)
{
return (x + ((x >> 31) & ((1 << n) + ~0))) >> n;
}
我理解x >> 31
部分(如果x
为负数,则仅添加下一部分,因为如果它为正x
将自动向0舍入)。但令我困扰的是(1 << n) + ~0
部分。它怎么样?
答案 0 :(得分:7)
假设2补码,只是位移的股息相当于某种划分:不是传统的划分,我们将被除数除以下一个除数的倍数。但另一种我们将股息向负无穷大方向发展。我重新发现,在Smalltalk中,请参阅http://smallissimo.blogspot.fr/2015/03/is-bitshift-equivalent-to-division-in.html。
例如,让我们将-126除以8.传统上,我们会写
-126 = -15 * 8 - 6
但是如果我们向无穷大方向前进,我们会得到一个积极的余数并写下来:
-126 = -16 * 8 + 2
位移正在执行第二个操作,就位模式而言(假设为了短的8位长int):
1000|0010 >> 3 = 1111|0000
1000|0010 = 1111|0000 * 0000|1000 + 0000|0010
那么,如果我们希望传统的除数与商为零并且剩余的相同符号作为股息呢?很简单,我们只需要为商添加1 - 当且仅当被除数为负数且除法不精确时。
你看到x>>31
对应第一个条件,被除数是负数,假设int有32位。
第二个术语对应于第二个条件,如果除法是不精确的。
看看如何编码-1,-2,-4,......在两个补码中:1111 | 1111,1111 | 1110,1111 | 1100。因此,对n的2次幂的否定有n个尾随零。
当被除数有n个尾随零并且我们除以2 ^ n时,则不需要将1加到最终商。在任何其他情况下,我们需要添加1。
((1&lt; n)+〜0)正在做的是创建一个带有n个尾随的掩码。
最后一点并不重要,因为我们将向右移动并将它们扔掉。因此,如果除法是精确的,则除数的n个尾随位为零,我们只添加将被跳过的n 1。相反,如果除法是不精确的,那么被除数的n个尾随位中的一个或多个是1,我们肯定会导致进位到n + 1位:这就是我们如何加1到商(我们在红利中添加2 ^ n)。这可以解释一下吗?
答案 1 :(得分:2)
这是&#34;只写代码&#34;:而不是试图理解代码,尝试自己创建。
例如,让我们将数字除以8(向右移3)。 如果数字为负数,则正常的右移向错误的方向进行。让我们来修复&#34;它通过添加一个数字:
int divideBy8(int x)
{
if (x >= 0)
return x >> 3;
else
return (x + whatever) >> 3;
}
在这里,您可以为whatever
提出数学公式,或进行一些试验和错误。无论如何,这里whatever = 7
:
int divideBy8(int x)
{
if (x >= 0)
return x >> 3;
else
return (x + 7) >> 3;
}
如何统一这两个案例?你需要创建一个如下所示的表达式:
(x + stuff) >> 3
其中stuff
为7表示负数x
,0表示正数x
。这里的技巧是使用x >> 31
,它是一个32位数,其位等于x
的符号位:全0或全1.因此stuff
是
(x >> 31) & 7
将所有这些结合起来,并用2的更一般的幂替换8和7,你得到你所问的代码。
注意:在上面的描述中,我假设int
代表一个32位硬件寄存器,硬件使用两个补码表示来进行右移。
答案 2 :(得分:0)
OP的引用是C#
代码和许多微妙的差异导致它与C一起错误代码,因为这篇文章被标记了。
int
不一定是32位,因此使用32的幻数不能提供强大的解决方案。
特别是(1 << n) + ~0
会导致实现定义的行为,当n
导致某个位移动到符号位置时。编码不好。
将代码限制为仅使用&#34;二进制&#34;运算符<< >> + ^ ~ & | !
鼓励编码人员假设关于int
的内容,这些内容不可移植,也不符合C规范。因此,OP发布的代码并没有&#34;工作&#34;一般来说,虽然可以在许多常见的实现中工作。
当int
不是2补码时OP代码失败,不使用范围[-2147483648 .. 2147483647]
或1 << n
使用的实现行为不符合预期。
// weak code
int divideByPowerOf2(int x, int n) {
return (x + ((x >> 31) & ((1 << n) + ~0))) >> n;
}
一个简单的替代方案,假设long long
超出了int
的范围。我怀疑这符合OP目标的一些角落,但OP的既定目标鼓励非强健编码。
int divideByPowerOf2(int x, int n) {
long long ill = x;
if (x < 0) ill = -ill;
while (n--) ill >>= 1;
if (x < 0) ill = -ill;
return (int) ill;
}