位操作将最左侧的设置位转换为右侧的交替位?

Posted

技术标签:

【中文标题】位操作将最左侧的设置位转换为右侧的交替位?【英文标题】:Bit manipulation to convert leftmost set bits to right-side alternating bits? 【发布时间】:2019-08-21 02:00:32 【问题描述】:

我正在尝试最大限度地优化低级子程序,但我无法找出在这种特定情况下翻转位的最快方法:

给定一个二进制整数n,其中所有设置位都在所有清除位的左侧(例如111100001100001110000),是否有可能产生一个数字长度为@的二进制整数987654325@,设置所有偶数位并清除所有奇数位?

例子:

n = 111000, answer: 10
n = 1111000, answer: 1010
n = 110, answer: 0
n = 111110000000, answer: 101010
n = 1111111111000000000, answer: 1010101010101010

n 保证至少有 2 个set bits,并且至少有 (set bits - 1) clear bits

答案必须仅使用恒定数量的位操作和/或算术运算(即无循环),并且不能使用任何类型转换(仅整数,无字符串)。

【问题讨论】:

您的子程序的实际目的是什么?为什么要限制所有这些?描述读起来像家庭作业问题。 允许的操作集到底是什么?像 popcnt、lzcnt、pext 等“高级”的东西? (E:没有它们是可能的,但它会变得有点毛茸茸) @lurker 我正在设计一种算法来生成大小为n 的所有可能排列的二叉树,其中k 的不同值的数量。这是其中的一小部分。我猜这听起来很学术,但它确实有实际应用。我不是学生。 n 应该至少有 1 个清除位吗? “我正在尝试最大限度地优化低级子例程”未演示。发布您尝试过的其他内容,这只是请求其他人完成工作。 【参考方案1】:

一种方法是使用以下步骤:

    右对齐(丢弃尾随零) 去掉 2 个设置位 设置位数加倍 屏蔽偶数位

例如,仅使用“基本”操作:

// right-justify
x = x / (x & -x)
// get rid of 2 set bits
x >>= 2
// double the number of set bits
x *= x + 2
// mask out even bits
x &= 0xAAAAAAAAAAAAAAAA

这一步“设置位的数量加倍”依赖于x 是此时的二减一的幂。如果x 可以写成 2k-1 那么x * (x + 2) 将是 (2k-1) * (2k+ 1) = 22k-1 所以它的设置位数加倍。

除法不是很好,如果你有一个快速的tzcnt,那么你可以右对齐:

x >>= tzcnt(x)

使用快速pdep(Intel Haswell 和更新版本,在 AMD Ryzen 上运行但速度较慢),可以避免将设置位数加倍,

// spread out the bits to even positions
x = pdep(x, 0xAAAAAAAAAAAAAAAA)

而快速pext 可以用作另一种右对齐方式,

// right-justify
x = pext(x, x)

对于常见的popcnt,可以使用更直接的方法,计算设置位的数量,减去两个,然后生成该大小的模式,例如通过向右移动0xAAAAAAAAAAAAAAAA 直到它足够短,或者使用bzhi 在顶部截断它。

【讨论】:

这太棒了!如此清晰和简单。如果它有助于启用其他解决方案,我在原始问题中添加了对输入 n 的约束。开始设置/清除计数有一些保证。 @user107870 我想不出一种方法来保证尾随零的数量,如果它是一个更强的保证,比如tzcnt(x) == popcnt(x),那么它会让问题变得更容易跨度> @harold 我想知道,在此处描述的特定情况下,x / (x & -x) 是否不能被 x / (-x) 替换? @GuillaumePetitjean 我相信有一段时间(使用直觉“否定翻转最右边设置位上方的位”),但例如 -6 不是 2,很多更高的位被设置还有 如果你想避免硬编码整数长度, /3*2 就可以了。除法很慢,但是好的编译器可以用乘法代替除法,因为 3 是一个常数。 (只需检查 Godbolt 看看如何)【参考方案2】:

这是一个 Python 算法,它不关心第一个零位的右侧有多少个零位。它构建答案而不用担心从 1 位中减去 2 位,最后将结果向右移动 4 位以考虑到这一点。

k = 0
n = 0b111110000

while n != 0:
    if n & 1 != 0:
        k = (k << 2) + 2
    n >>= 1

print(bin(k>>4))

【讨论】:

OP 要求“请不要循环”

以上是关于位操作将最左侧的设置位转换为右侧的交替位?的主要内容,如果未能解决你的问题,请参考以下文章

转《python 位操作符 左移和右移 运算》

C语言位操作

java位运算

sprintf在具有中断的32位MCU中重新进入64位操作

InstallShield - 仅在 64 位操作系统时保存注册表

位运算