使用位操作将 8 字节数字中的每个字节转换为单个字节
Use bit manipulation to convert a bit from each byte in an 8-byte number to a single byte
我有一个 64 位无符号整数。我想检查每个字节的第 6 位和 return 代表第 6 位的单个字节。
显而易见的 "brute force" 解决方案是:
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
然后以某种方式将其转换为字节?
如果 _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>
,none 已经过测试。
一个更奇怪的选项是用乘法收集位:(仅略微测试)
int extracted = (num & 0x2020202020202020) * 0x08102040810204 >> 56;
这里的想法是 num & 0x2020202020202020
只设置了很少的位,因此我们可以安排一个从不携带我们需要的位(或者根本不携带)的乘积。构造乘数来执行此操作:
a0000000b0000000c0000000d0000000e0000000f0000000g0000000h0000000 +
0b0000000c0000000d0000000e0000000f0000000g0000000h00000000000000 +
00c0000000d0000000e0000000f0000000g0000000h000000000000000000000 etc..
然后最高字节将所有位 "compacted" 放在一起。较低的字节实际上也有类似的东西,但它们缺少必须来自 "higher" 的位(位只能在乘法中向左移动)。
我有一个 64 位无符号整数。我想检查每个字节的第 6 位和 return 代表第 6 位的单个字节。
显而易见的 "brute force" 解决方案是:
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
然后以某种方式将其转换为字节?
如果 _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>
,none 已经过测试。
一个更奇怪的选项是用乘法收集位:(仅略微测试)
int extracted = (num & 0x2020202020202020) * 0x08102040810204 >> 56;
这里的想法是 num & 0x2020202020202020
只设置了很少的位,因此我们可以安排一个从不携带我们需要的位(或者根本不携带)的乘积。构造乘数来执行此操作:
a0000000b0000000c0000000d0000000e0000000f0000000g0000000h0000000 +
0b0000000c0000000d0000000e0000000f0000000g0000000h00000000000000 +
00c0000000d0000000e0000000f0000000g0000000h000000000000000000000 etc..
然后最高字节将所有位 "compacted" 放在一起。较低的字节实际上也有类似的东西,但它们缺少必须来自 "higher" 的位(位只能在乘法中向左移动)。