将有符号整数除以2的幂

时间:2016-09-25 21:03:33

标签: c binary bit-manipulation

我正在研究一种方法,只使用二元运算符(<<>> + ^〜& |!)将有符号整数除以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部分。它怎么样?

3 个答案:

答案 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;
}