我正在尝试将uint16_t
输入转换为uint32_t
位掩码。输入中的一位在输出位掩码中切换两位。以下是将4位输入转换为8位位掩码的示例:
Input Output
ABCDb -> AABB CCDDb
A,B,C,D are individual bits
Example outputs:
0000b -> 0000 0000b
0001b -> 0000 0011b
0010b -> 0000 1100b
0011b -> 0000 1111b
....
1100b -> 1111 0000b
1101b -> 1111 0011b
1110b -> 1111 1100b
1111b -> 1111 1111b
有没有一种方法可以实现这种行为?
答案 0 :(得分:7)
Interleaving bits by Binary Magic Numbers包含了线索:
uint32_t expand_bits(uint16_t bits)
{
uint32_t x = bits;
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
return x | (x << 1);
}
前四个步骤以8位,4位,2位,1位为一组连续交错源位,零位,第一步后00AB00CD
,第二步后0A0B0C0D
,等等。最后一步将每个偶数位(包含一个原始源位)复制到相邻的奇数位中,从而实现所需的位排列。
有许多变体是可能的。最后一步也可以编码为x + (x << 1)
或3 * x
。前四个步骤中的|
运算符可以由^
运算符替换。掩码也可以修改,因为一些位自然为零,不需要清除。在一些处理器上,短掩模可以作为中间体结合到机器指令中,减少了构造和/或加载掩模常数的努力。增加无序处理器的指令级并行性并针对具有shift-add或整数乘加指令的那些进行优化也可能是有利的。包含各种这些想法的一个代码变体是:
uint32_t expand_bits (uint16_t bits)
{
uint32_t x = bits;
x = (x ^ (x << 8)) & ~0x0000FF00;
x = (x ^ (x << 4)) & ~0x00F000F0;
x = x ^ (x << 2);
x = ((x & 0x22222222) << 1) + (x & 0x11111111);
x = (x << 1) + x;
return x;
}
答案 1 :(得分:6)
将4位输入映射到8位输出的最简单方法是使用16个输入表。那么它只需要从uint16_t
一次提取4位,进行表查找,并将8位值插入输出中。
uint32_t expandBits( uint16_t input )
{
uint32_t table[16] = {
0x00, 0x03, 0x0c, 0x0f,
0x30, 0x33, 0x3c, 0x3f,
0xc0, 0xc3, 0xcc, 0xcf,
0xf0, 0xf3, 0xfc, 0xff
};
uint32_t output;
output = table[(input >> 12) & 0xf] << 24;
output |= table[(input >> 8) & 0xf] << 16;
output |= table[(input >> 4) & 0xf] << 8;
output |= table[ input & 0xf];
return output;
}
这在性能和可读性之间提供了适当的折衷。它没有完全具有cmaster的顶级查找解决方案的性能,但它肯定比thndrwrks&#39;更容易理解。神奇的神秘解决方案。因此,它提供了一种技术,可以应用于更多种类的问题,即使用小型查找表来解决更大的问题。
答案 2 :(得分:3)
如果你想得到一些相对速度的估计,一些社区维基测试代码。根据需要进行调整。
void f_cmp(uint32_t (*f1)(uint16_t x), uint32_t (*f2)(uint16_t x)) {
uint16_t x = 0;
do {
uint32_t y1 = (*f1)(x);
uint32_t y2 = (*f2)(x);
if (y1 != y2) {
printf("%4x %8lX %8lX\n", x, (unsigned long) y1, (unsigned long) y2);
}
} while (x++ != 0xFFFF);
}
void f_time(uint32_t (*f1)(uint16_t x)) {
f_cmp(expand_bits, f1);
clock_t t1 = clock();
volatile uint32_t y1 = 0;
unsigned n = 1000;
for (unsigned i = 0; i < n; i++) {
uint16_t x = 0;
do {
y1 += (*f1)(x);
} while (x++ != 0xFFFF);
}
clock_t t2 = clock();
printf("%6llu %6llu: %.6f %lX\n", (unsigned long long) t1,
(unsigned long long) t2, 1.0 * (t2 - t1) / CLOCKS_PER_SEC / n,
(unsigned long) y1);
fflush(stdout);
}
int main(void) {
f_time(expand_bits);
f_time(expandBits);
f_time(remask);
f_time(javey);
f_time(thndrwrks_expand);
// now in the other order
f_time(thndrwrks_expand);
f_time(javey);
f_time(remask);
f_time(expandBits);
f_time(expand_bits);
return 0;
}
结果
0 280: 0.000280 FE0C0000 // fast
280 702: 0.000422 FE0C0000
702 1872: 0.001170 FE0C0000
1872 3026: 0.001154 FE0C0000
3026 4399: 0.001373 FE0C0000 // slow
4399 5740: 0.001341 FE0C0000
5740 6879: 0.001139 FE0C0000
6879 8034: 0.001155 FE0C0000
8034 8470: 0.000436 FE0C0000
8486 8751: 0.000265 FE0C0000
答案 3 :(得分:2)
这是一个有效的实施方案:
uint32_t remask(uint16_t x)
{
uint32_t i;
uint32_t result = 0;
for (i=0;i<16;i++) {
uint32_t mask = (uint32_t)x & (1U << i);
result |= mask << (i);
result |= mask << (i+1);
}
return result;
}
在循环的每次迭代中,来自uint16_t
的相关位被屏蔽并存储。
然后将该位移位到其位位置并对结果进行“或”运算,然后再次移位其位加1并对结果进行“或”运算。
答案 4 :(得分:2)
一个简单的循环。也许不够苛刻?
uint32_t thndrwrks_expand(uint16_t x) {
uint32_t mask = 3;
uint32_t y = 0;
while (x) {
if (x&1) y |= mask;
x >>= 1;
mask <<= 2;
}
return y;
}
尝试另一个快两倍的速度。仍为655/272,与expand_bits()
一样慢。似乎是最快的16循环迭代解决方案。
uint32_t thndrwrks_expand(uint16_t x) {
uint32_t y = 0;
for (uint16_t mask = 0x8000; mask; mask >>= 1) {
y <<= 1;
y |= x&mask;
}
y *= 3;
return y;
}
答案 5 :(得分:2)
如果您关心的是性能和简单性,那么最好使用大型查找表(每个4字节的64k条目)。有了它,您几乎可以使用任何您喜欢的算法来生成表,查找只是一个内存访问。
如果该表太大而不适合您,您可以将其拆分。例如,您可以使用8位查找表,其中256个条目各有2个字节。有了它,您只需两次查找即可执行整个操作。额外的是,这种方法允许类型惩罚技巧,以避免使用位操作分割地址的麻烦:
//Implementation defined behavior ahead:
//Works correctly for both little and big endian machines,
//however, results will be wrong on a PDP11...
uint32_t getMask(uint16_t input) {
assert(sizeof(uint16_t) == 2);
assert(sizeof(uint32_t) == 4);
static const uint16_t lookupTable[256] = { 0x0000, 0x0003, 0x000c, 0x000f, ... };
unsigned char* inputBytes = (unsigned char*)&input; //legal because we type-pun to char, but the order of the bytes is implementation defined
char outputBytes[4];
uint16_t* outputShorts = (uint16_t*)outputBytes; //legal because we type-pun from char, but the order of the shorts is implementation defined
outputShorts[0] = lookupTable[inputBytes[0]];
outputShorts[1] = lookupTable[inputBytes[1]];
uint32_t output;
memcpy(&output, outputBytes, 4); //can't type-pun directly from uint16 to uint32_t due to strict aliasing rules
return output;
}
上面的代码围绕严格的别名规则工作,只是强制转换为char
,这是严格别名规则的明确例外。它还通过以与输入分割相同的顺序构建结果来解决小/大端字节顺序的影响。但是,它仍然暴露了实现定义的行为:字节顺序为1, 0, 3, 2
或其他middle endian orders的计算机将无声地产生错误的结果(实际上有像PDP11这样的CPU。 ..)。
当然,您可以进一步拆分查找表,但我怀疑这对您有什么好处。
答案 6 :(得分:1)
试试这个,其中a
b
start_flag
c
d
e
end_flag
c
d
e
end_flag
f
g
是uint16_t输入掩码:
input16
答案 7 :(得分:0)
我的解决方案是在主流x86 PC上运行,简单而通用。我没有写这个来竞争最快和/或最短的实现。这只是解决OP提交的问题的另一种方式。
Ecto.DateTime