使用位操作将8字节数字中的每个字节的位转换为单字节

时间:2017-10-21 18:24:35

标签: c++ bit-manipulation bitmask

我有一个64位无符号整数。我想检查每个字节的第6位并返回表示第6位的单个字节。

明显的"蛮力"解决方案是:

inline const unsigned char Get6thBits(unsigned long long num) {
    unsigned char byte(0);
    for (int i = 7; i >= 0; --i) {
        byte <<= 1;
        byte |= bool((0x20 << 8 * i) & num);
    }

    return byte;
}

我可以将循环展开到一堆连接的|语句中以避免int分配,但这仍然非常难看。

有更快,更聪明的方法吗?也许使用位掩码来获取第6位0x2020202020202020,然后以某种方式将其转换为字节?

1 个答案:

答案 0 :(得分:4)

如果_pext_u64是可能的(这将适用于Haswell和更新,但在Ryzen上它很慢),你可以这样写:

int extracted = _pext_u64(num, 0x2020202020202020);

这是实现它的一种真正的文字方式。 pext接受一个值(第一个参数)和一个掩码(第二个参数),在掩码有一个设置位的每个位置,它从值中获取相应的位,并且所有位都连接在一起。

_mm_movemask_epi8可以更广泛地使用,您可以像这样使用它:

__m128i n = _mm_set_epi64x(0, num);
int extracted = _mm_movemask_epi8(_mm_slli_epi64(n, 2));

pmovmskb获取其输入向量中每个字节的高位并连接它们。我们想要的位不是每个字节的高位,所以我用psllq将它们移动到两个位置(当然你可以直接移位num)。 _mm_set_epi64x只是将num放入向量的一种方式。

不要忘记#include <intrin.h>,而且这些都没有经过测试。

Codegen seems reasonable enough

一个更奇怪的选项是使用乘法收集位:(仅经过轻微测试)

int extracted = (num & 0x2020202020202020) * 0x08102040810204 >> 56;

这里的想法是num & 0x2020202020202020只设置了很少的位,所以我们可以安排一个永远不会带有我们需要(甚至根本不需要)的位的产品。构造乘数是为了这样做:

a0000000b0000000c0000000d0000000e0000000f0000000g0000000h0000000 +
0b0000000c0000000d0000000e0000000f0000000g0000000h00000000000000 +
00c0000000d0000000e0000000f0000000g0000000h000000000000000000000 etc..

然后顶部字节将包含所有位&#34;压缩&#34;一起。较低的字节实际上也有类似的东西,但它们缺少必须来自&#34;更高&#34; (位只能在乘法中向左移动)。