如何使用按位运算补充最右边的位,保持前导零位为零?

时间:2015-02-07 13:21:40

标签: c bit-manipulation bit

如何补充具有前导零位的数值,以便前导零位保持为零,剩余的1位和0位将被补充?我想仅通过按位运算来执行此操作,而不必检查该值以确定该值中有多少前导零位。我可以使用哪些按位运算来仅隔离包含一个位或一个位的值的最低有效部分,并仅补充该部分值,使前导零位保持不变。

例如,给出一个数字,比如9。

9将以无符号32位二进制形式表示为00 ... 01001。

为简单起见,请仅考虑8位格式。 9 = 00001001

现在当我补充这个数字时,我会得到 11110110

但这不是我想要的。

我希望原始表示的前导0保持原样并补充其余部分。

即。对于9 = 00001001, 前4个零应该保持为零,下一部分应该称赞。 所以我将有00000110即6。

我知道更长的方法:

  1. 查找给定数字的位数b
  2. 查找给定数字的补充说x
  3. 提取最后b
  4. 或者

    1. (0xFF<<b)
    2. 中减去x

2 个答案:

答案 0 :(得分:7)

如果您有想要影响的所有位的掩码,那么它只是x ^ mask(XORing有点补充它)。

获得面具并不难:

mask = x;
mask |= mask >> 1;
mask |= mask >> 2;
mask |= mask >> 4;
mask |= mask >> 8;
mask |= mask >> 16;

那是32位的。根据需要使用更多(或更少)步骤。

这种结构将最高设置位扩展到所有低位,方法是将该位已被复制到的所有位置并将其与该块右侧的位进行或运算,如下所示:

01000000
01100000
01111000
01111111

最高设置位右侧的任何设置位也会被复制,但它们不会干扰该过程,因为受其影响的任何位都位于最高设置位的右侧,因此应该是无论如何都要设置。

根据您所使用的机器,可能有更好的方法来获取该面具。以下是x64的一些选项。

使用shrx(Haswell +,轻松修改为更便携)

mov rdx, -1
bsr rax, rax
cmovz rdx, rax
xor eax, 63
shrx rax, rdx, rax

使用shrxlzcnt(Haswell +)

lzcnt rax, rax
sbb rdx, rdx
not rdx
shrx rax, rdx, rax

使用lzcntbzhi(Haswell +)

lzcnt rax, rax
mov edx, 64
sub edx, eax
mov rax, -1
bzhi rax, rax, rdx

如果你可以反转位,就像这样:

rbit r0, r0
neg r1, r0
or r0, r1
rbit r0, r0

这取决于2的补码否定的性质,即最右边的设置位左边的所有位都被补充 [1] 。与其补码的OR运算是1,因此-x | x将最右边的1传播到其左侧的所有位。这与我们需要的相反,但是通过快速位反转它很有用。

[1]:证明草图:-x = ~x + 1,考虑到最右边的1位,在补充之后它们将是01 *的形式,加上一个恢复原来的10 *而它上面的位仍然是补充的。

答案 1 :(得分:2)

在我看来,你需要检查值中的位,以便构建一个掩码,使其只能处理你想要工作的位。

以下似乎是便携式最佳案例。这使用unsigned long作为函数类型以允许升级和减少,以便它可以与字节(8位或unsigned char),字(16位或unsigned short)或双精度一起使用字(32位或unsigned long)变量。如果您需要64位,那么您可以使用unsigned long long中的ulComplLeastSig()ulMaskulBit相应的值更改。

此代码构建一个掩码,然后使用按位运算来消除应为零的前导位。查看使用Visual Studio发布版本为函数生成的机器代码,代码非常紧凑,变量保存在循环中的寄存器中。

unsigned long ulComplLeastSig (unsigned long ulValue)
{
    unsigned long ulMask = 0xffffffff;
    unsigned long ulBit  = 0x80000000;

    for (; ulBit; ulBit >>= 1) {
        // beginning with the most significant bit, turn off bits in the mask
        // until we find the first on bit in the value. this creates our
        // mask to remove leading zeros after we complement.
        if (ulBit & ulValue) break; else ulMask ^= ulBit;
    }
    return (ulMask & (~ulValue));
}

int _tmain(int argc, _TCHAR* argv[])
{
    unsigned long  ulValue = 9;
    unsigned long  ulNewValue = 0;
    unsigned short usValue = 9;
    unsigned short usNewValue = 0;

    ulNewValue = ulComplLeastSig (ulValue);

    // use the function with an unsigned short. cast the return value
    // to remove compiler warnings. depend on promotion for the function
    // argument.
    usNewValue = (unsigned short)ulComplLeastSig (usValue);

    return 0;
}

修改

考虑到这一点,我想知道是否有可能仅使用位操作来消除循环中的if语句,并提出了这种可能性。

unsigned long ulComplLeastSig_2 (unsigned long ulValue)
{
    unsigned long ulMask = 0xffffffff;
    unsigned long ulBit  = 0x80000000;

    // complement the value so that we are ready to start
    // creating our mask.  the goal is to create a mask
    // that will get rid of the leading ON bits from the
    // complemented value by starting with all the bits
    // of the mask turned on then moving through the
    // complemented value bit by bit turning off bits in the
    // mask until we need to stop.
    ulValue = ~ulValue;
    for (; ulBit; ulBit >>= 1) {
        ulBit &= (ulBit ^ (ulMask ^= (ulBit & ulValue)));
    }
    return (ulMask & ulValue);
}