【问题标题】:How to complement right most bits keeping leading zero bits zero using bitwise operations?如何使用按位运算补充最右边的位以保持前导零位为零?
【发布时间】:2015-04-07 14:07:53
【问题描述】:

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

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

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

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

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

但这不是我想要的。

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

即对于 9 = 00001001, 前 4 个零应该保持为零,并且应该补充下一部分。 所以我会有 00000110 即 6。

我知道更长一点的方法:

  1. 找出给定数字是多少位b
  2. 查找给定数字的补码说x
  3. 提取最后一个b

或者

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

【问题讨论】:

  • 是的,这两种方法都应该有效。
  • @OliverCharlesworth 他们确实有效,我正在寻找更好的方法!

标签: c bit-manipulation bit


【解决方案1】:

如果您拥有想要影响的所有位的掩码,则只需 x ^ mask(异或位与 1 互补)。

得到那个面具并不难:

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]。与补码的位或运算为 1,因此-x | x 将最右边的 1 传播到其左侧的所有位。这与我们所需要的相反,但是快速反转它很有用。

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

【讨论】:

  • 答案非常好,但解释得很糟糕,以至于它似乎是某种魔法。我也不确定我是否真的理解它,但无论如何这是我的理解。至少有两种方法可以对一个值进行补码,一种是使用补码运算符,另一种是使用全 1 的掩码进行 XOR。对所有 1 使用 XOR,原始值中的 1 将切换为 0,原始值中的 0 将切换为 1。那么如何生成一个掩码,其前导位为 0,而以最高位开头的位为 0?一系列右移填充最右边的一位。
  • @RichardChambers 这样更好吗?
  • 这是一个很大的变化!如果我可以再次 +1,我会的。我想最后一个问题是如何知道要移位的位数以及根据可变大小进行多少次移位。例如,如果它是 16 位字,我是向上移位 8 还是只移位 4?移位 4 移动一个半字节,而移位 8 移动一个字节,因此对于 16 位字,移位 8,对于 32 位字,移位 16,对于 64 位字,移位 32 似乎。对吗?
  • @RichardChambers 是的,最多(但不包括)大小(由于显而易见的原因,按整个大小移动是无用的),你可以(最多)每次加倍移位计数,所以你以 ceil(log2(bits)) 步骤结束。顺便可以任意重新排列步骤。
【解决方案2】:

在我看来,您需要检查值中的位,以便构建一个掩码,以便仅与您想要使用的位一起使用。

以下内容似乎是便携的最佳案例。这将unsigned long 用于函数类型以允许提升和减少,以便它可以与字节(8 位或unsigned char)、字(16 位或unsigned short)或双字(32 位或@ 987654324@) 变量。如果您需要 64 位,那么您可以在函数 ulComplLeastSig() 中使用 unsigned long long,并对 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);
}

【讨论】:

  • 没有冒犯,但为什么这会比我的掩码并行前缀生成更好?这将是一个很长的循环,其中的分支预测错误
  • @harold,从计算上来说,不确定会不会更好。它易于理解且易于操作,并且应该可移植到各种架构中。可以通过将类型更改为不同的类型大小来定制它,例如,如果这是一个带有无符号短裤的应用程序,则将unsigned long 更改为unsigned short。我不确定问题是否来自某种嵌入式应用程序,板上有廉价的微型计算机或计算机或其他什么。
猜你喜欢
  • 1970-01-01
  • 2011-02-08
  • 1970-01-01
  • 2012-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-31
  • 2014-12-19
相关资源
最近更新 更多