位操作将最左侧的设置位转换为右侧的交替位?
Posted
技术标签:
【中文标题】位操作将最左侧的设置位转换为右侧的交替位?【英文标题】:Bit manipulation to convert leftmost set bits to right-side alternating bits? 【发布时间】:2019-08-21 02:00:32 【问题描述】:我正在尝试最大限度地优化低级子程序,但我无法找出在这种特定情况下翻转位的最快方法:
给定一个二进制整数n
,其中所有设置位都在所有清除位的左侧(例如11110000
、110000
、1110000
),是否有可能产生一个数字长度为@的二进制整数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 要求“请不要循环”以上是关于位操作将最左侧的设置位转换为右侧的交替位?的主要内容,如果未能解决你的问题,请参考以下文章