xce*_*cel 20 c++ boolean bit-manipulation bit-packing
我有8个bool变量,我想将它们"合并"成一个字节.
有一个简单/首选的方法来做到这一点?
相反,如何将一个字节解码为8个独立的布尔值?
我认为这不是一个不合理的问题,但由于我无法通过谷歌找到相关文档,它可能是另一个"非你所有直觉都是错误的"案例.
rod*_*igo 20
困难的方式:
unsigned char ToByte(bool b[8])
{
unsigned char c = 0;
for (int i=0; i < 8; ++i)
if (b[i])
c |= 1 << i;
return c;
}
Run Code Online (Sandbox Code Playgroud)
和:
void FromByte(unsigned char c, bool b[8])
{
for (int i=0; i < 8; ++i)
b[i] = (c & (1<<i)) != 0;
}
Run Code Online (Sandbox Code Playgroud)
或酷的方式:
struct Bits
{
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
};
union CBits
{
Bits bits;
unsigned char byte;
};
Run Code Online (Sandbox Code Playgroud)
然后,您可以分配给联盟的一个成员并从另一个成员中读取.但请注意,位的顺序Bits是实现定义的.
inline uint8_t pack8bools(bool* a)
{
uint64_t t;
memcpy(&t, a, sizeof t); // strict-aliasing & alignment safe load
return 0x8040201008040201ULL*t >> 56;
// bit order: a[0]<<7 | a[1]<<6 | ... | a[7]<<0 on little-endian
// for a[0] => LSB, use 0x0102040810204080ULL on little-endian
}
void unpack8bools(uint8_t b, bool* a)
{
// on little-endian, a[0] = (b>>7) & 1 like printing order
auto MAGIC = 0x8040201008040201ULL; // for opposite order, byte-reverse this
auto MASK = 0x8080808080808080ULL;
uint64_t t = ((MAGIC*b) & MASK) >> 7;
memcpy(a, &t, sizeof t); // store 8 bytes without UB
}
Run Code Online (Sandbox Code Playgroud)
假设 sizeof(bool) == 1
要可移植地执行 LSB <-> a[0](如pext/pdep下面的版本)而不是使用主机字节序的反面,请htole64(0x0102040810204080ULL)在两个版本中用作魔术倍增器。(htole64来自 BSD / GNU <endian.h>)。这会安排乘法器字节以匹配 bool 数组的小端顺序。 htobe64使用相同的常量给出另一个顺序,MSB-first,就像你用来打印基数为 2 的数字一样。
您可能希望确保 bool 数组是 8 字节对齐 ( alignas(8)) 以提高性能,并且编译器知道这一点。 memcpy对于任何对齐总是安全的,但在需要对齐的 ISA 上,如果编译器memcpy知道指针已充分对齐,则它只能作为单个加载或存储指令内联。 *(uint64_t*)a将承诺对齐,但也违反了严格的别名规则。即使在允许未对齐负载的 ISA 上,它们在自然对齐时也可以更快。但是编译器仍然可以内联 memcpy 而不会在编译时看到该保证。
假设我们有 8 个布尔值b[0],它们b[7]的最低有效位分别命名为 ah,我们想将它们打包成一个字节。将这 8 个连续的bools 视为一个 64 位字并加载它们,我们将在小端机器中以相反的顺序获得这些位。现在我们将进行乘法运算(这里的点是零位)
| b7 || b6 || b4 || b4 || b3 || b2 || b1 || b0 |
.......h.......g.......f.......e.......d.......c.......b.......a
× 1000000001000000001000000001000000001000000001000000001000000001
????????????????????????????????????????????????????????????????
?......h.?.....g..?....f...?...e....?..d.....?.c......?b.......a
?.....g..?....f...?...e....?..d.....?.c......?b.......a
?....f...?...e....?..d.....?.c......?b.......a
+ ?...e....?..d.....?.c......?b.......a
?..d.....?.c......?b.......a
?.c......?b.......a
?b.......a
a
????????????????????????????????????????????????????????????????
= abcdefghxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Run Code Online (Sandbox Code Playgroud)
添加了箭头,以便更容易看到幻数中设置位的位置。此时 8 个最低有效位已放入最高字节,我们只需要将其余位屏蔽掉
所以包装的神奇数字是0b1000000001000000001000000001000000001000000001000000001000000001or 0x8040201008040201。如果您使用的是大端机器,则需要使用0x0102040810204080以类似方式计算的幻数
对于解包,我们可以做一个类似的乘法
| b7 || b6 || b4 || b4 || b3 || b2 || b1 || b0 |
abcdefgh
× 1000000001000000001000000001000000001000000001000000001000000001
????????????????????????????????????????????????????????????????
= h0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh
& 1000000010000000100000001000000010000000100000001000000010000000
????????????????????????????????????????????????????????????????
= h0000000g0000000f0000000e0000000d0000000c0000000b0000000a0000000
Run Code Online (Sandbox Code Playgroud)
相乘后,我们在最重要的位置有了所需的位,因此我们需要屏蔽掉不相关的位并将剩余的位移到最不重要的位置。输出将是包含 a 到 h 的小端字节。
在与新的x86 CPU BMI2有PEXT和PDEP用于此目的的说明。pack8bools上面的函数可以替换为
_pext_u64(*((uint64_t*)a), 0x0101010101010101ULL);
Run Code Online (Sandbox Code Playgroud)
该unpack8bools功能可以实现为
_pdep_u64(b, 0x0101010101010101ULL);
Run Code Online (Sandbox Code Playgroud)
(这映射 LSB -> LSB,就像一个0x0102040810204080ULL乘数常数,与0x8040201008040201ULL.x86相反的是小端:a[0] = (b>>0) & 1;在 memcpy 之后。)
不幸的是,这些指令在 AMD 上非常慢,因此您可能需要与上面的乘法方法进行比较,看看哪个更好
#include <stdint.h> // to get the uint8_t type
uint8_t GetByteFromBools(const bool eightBools[8])
{
uint8_t ret = 0;
for (int i=0; i<8; i++) if (eightBools[i] == true) ret |= (1<<i);
return ret;
}
void DecodeByteIntoEightBools(uint8_t theByte, bool eightBools[8])
{
for (int i=0; i<8; i++) eightBools[i] = ((theByte & (1<<i)) != 0);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
18307 次 |
| 最近记录: |