你会怎么用C做的? (例如:如果我们必须镜像8位,则10110001变为10001101)。某些处理器上是否有任何可以简化此任务的说明?
答案 0 :(得分:8)
它实际上称为“位反转”,通常在FFT加扰中完成。 O(log N)方式是(最多32位):
uint32_t reverse(uint32_t x, int bits)
{
x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1); // Swap _<>_
x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2); // Swap __<>__
x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4); // Swap ____<>____
x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8); // Swap ...
x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16); // Swap ...
return x >> (32 - bits);
}
也许这个小的“可视化”有助于:
前3个分配的示例,带有uint8_t
示例:
b7 b6 b5 b4 b3 b2 b1 b0
-> <- -> <- -> <- -> <-
----> <---- ----> <----
----------> <----------
好吧,如果我们正在做ASCII艺术,那么这是我的:
7 6 5 4 3 2 1 0
X X X X
6 7 4 5 2 3 0 1
\ X / \ X /
X X X X
/ X \ / X \
4 5 6 7 0 1 2 3
\ \ \ X / / /
\ \ X X / /
\ X X X /
X X X X
/ X X X \
/ / X X \ \
/ / / X \ \ \
0 1 2 3 4 5 6 7
它有点像FFT蝴蝶。这就是它弹出FFT的原因。
答案 1 :(得分:3)
Per Rich Schroeppel在这个MIT memo中(如果你可以阅读汇编程序),下面将反转8位字节中的位,前提是你有64位算术可用:
byte = (byte * 0x0202020202ULL & 0x010884422010ULL) % 1023;
比特输出哪种风扇(乘法),选择它们(和),然后将它们缩小(模数)。
它实际上是8位数量吗?
答案 2 :(得分:2)
天真/慢/简单的方法是提取输入的低位并将其转换为另一个累积返回值的变量。
#include <stdint.h>
uint32_t mirror_u32(uint32_t input) {
uint32_t returnval = 0;
for (int i = 0; i < 32; ++i) {
int bit = input & 0x01;
returnval <<= 1;
returnval += bit; // Shift the isolated bit into returnval
input >>= 1;
}
return returnval;
}
对于其他类型,存储位数为sizeof(input) * CHAR_BIT
,但包括不属于该值的潜在填充位。固定宽度类型在这里是一个好主意。
+=
代替|=
使得gcc更有效地为x86编译它(使用x86的移位和添加指令,LEA)。当然,有更快的位反转方式;看到其他答案。这个循环适用于小代码大小(没有大掩码),但在其他方面几乎没有优势。
遗憾的是,编译器无法将此循环识别为位反转并将其优化为ARM rbit
或其他任何内容。 (见on the Godbolt compiler explorer)
答案 3 :(得分:2)
几乎与Most Efficient Algorithm for Bit Reversal ( from MSB->LSB to LSB->MSB) in C重复(有很多答案,包括一个AVX2答案,用于反转数组中的每个8位字符)。
在带有SSSE3 的x86上(Core2和更高版本,Bulldozer和更高版本),pshufb
(_mm_shuffle_epi8
)可以用作蚕食LUT来执行16并行查找。对于单个32位整数中的8个半字节,您只需要进行8次查找,但是真正的问题是将输入字节拆分为单独的半字节(上半部分为零)。与基于pshufb
的popcount基本相同。
avx2 register bits reverse 显示了如何对32位元素的压缩向量执行此操作。移植到128位向量的相同代码可以在AVX上很好地编译。
它对于单个32位int仍然很好,因为x86在整数和向量regs int bitrev = _mm_cvtsi128_si32 ( rbit32( _mm_cvtsi32_si128(input) ) );
之间具有非常有效的往返。只需花费额外的2条movd
指令即可将整数从整数寄存器获取到XMM中并返回。 (往返延迟=在Haswell之类的Intel CPU上为3个周期。)
rbit
具有单周期延迟,并且在一条指令中执行整个32位整数。
答案 4 :(得分:1)
最快的方法几乎肯定是查找表:
out[0]=lut[in[3]];
out[1]=lut[in[2]];
out[2]=lut[in[1]];
out[3]=lut[in[0]];
或者,如果您能够负担得起128k的表数据(通过负担得起,我的意思是cpu缓存利用率,而不是主内存或虚拟内存利用率),请使用16位单位:
out[0]=lut[in[1]];
out[1]=lut[in[0]];
答案 5 :(得分:1)
我还想出了一个只在16位临时空间中镜像4位(半字节)的最小解决方案。
mirr = ( (orig * 0x222) & 0x1284 ) % 63
答案 6 :(得分:0)
我想我会创建一个bitpatterns 0-255的查找表。读取每个字节并使用查找表反转该字节,然后适当地排列结果字节。
答案 7 :(得分:0)
quint64 mirror(quint64 a,quint8 l=64) {
quint64 b=0;
for(quint8 i=0;i<l;i++) {
b|=(a>>(l-i-1))&((quint64)1<<i);
}
return b;
}
此函数镜像少于64位。例如,它可以镜像12位。
quint64和quint8在Qt中定义。但无论如何都有可能重新定义它。
答案 8 :(得分:0)
如果您一直凝视着Mike DeSimone's great answer(像我一样),这是前3个作业的“可视化效果”,并以uint8_t
为例:
b7 b6 b5 b4 b3 b2 b1 b0
-> <- -> <- <- -> <- ->
----> <---- ----> <----
----------> <----------
因此,首先是按位交换,然后是“两位组”交换,依此类推。
答案 9 :(得分:0)
如果您对更嵌入式方法感兴趣,当我使用armv7a
系统时,我发现了RBIT
命令。
因此,在使用GNU extended asm
的C函数中,我可以使用:
uint32_t bit_reverse32(uint32_t inp32)
{
uint32_t out = 0;
asm("RBIT %0, %1" : "=r" (out) : "r" (inp32));
return out;
}
有些编译器公开了这样的内部C包装器。 (armcc __rbit
和gcc
也通过ACLE具有一些内在函数,但是使用gcc-arm-linux-gnueabihf
我找不到__rbit
C,所以我想出了上层代码。 / p>
我没有看,但是我想在其他平台上您可以创建类似的解决方案。
答案 10 :(得分:0)
当然,大多数人不会认为我的方法既不优雅也不高效:它的目的是便携,并且以某种方式“直截了当”。
#include <limits.h> // CHAR_BIT
unsigned bit_reverse( unsigned s ) {
unsigned d;
int i;
for( i=CHAR_BIT*sizeof( unsigned ),d=0; i; s>>=1,i-- ) {
d <<= 1;
d |= s&1;
}
return d;
}
此函数从源 bistring s
中提取最低有效位并将其作为最高有效位推送到目标 bitstring d
.
您可以使用适合您情况的任何数据类型替换 unsigned
数据类型,从 unsigned char
(CHAR_BIT
位,通常为 8)到 unsigned long long
(现代 64 位中的 128 位) CPU)。
当然,可以使用特定于 CPU 的指令(或指令集)代替我的纯 C 代码。
但不是“C 语言”,而是 C 包装器中的汇编指令。
答案 11 :(得分:-1)
int mirror (int input)
{// return bit mirror of 8 digit number
int tmp2;
int out=0;
for (int i=0; i<8; i++)
{
out = out << 1;
tmp2 = input & 0x01;
out = out | tmp2;
input = input >> 1;
}
return out;
}