繁体   English   中英

如何检查 8 位无符号字符中的设置位数?

[英]How to check the number of set bits in an 8-bit unsigned char?

所以我必须在 C 中找到 unsigned char 变量的设置位(在 1 上)?

一个类似的问题是如何计算 32 位整数中设置的位数? 但它使用的算法不容易适应 8 位无符号字符(或不明显)。

问题中建议的算法如何计算32位整数中的设置位数? 简单地适应了8位:

int NumberOfSetBits( uint8_t b )
{
     b = b - ((b >> 1) & 0x55);
     b = (b & 0x33) + ((b >> 2) & 0x33);
     return (((b + (b >> 4)) & 0x0F) * 0x01);
}

这只是将常数的最低有效八位缩短,然后删除最后的24位右移的情况。 同样可以使用8位移位将其调整为16位。 请注意,如果是8位,则32位算法的机械适配会导致冗余* 0x01 ,可以将其省略。

对于8位变量,最快的方法是使用查找表。

构建一个256个值的数组,每8位组合一个。 每个值应在其相应索引中包含位数:

int bit_count[] = {
// 00 01 02 03 04 05 06 07 08 09 0a, ... FE FF
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, ..., 7, 8
};

获取组合计数与从bit_count数组中查找值相同。 这种方法的优点是非常快。

您可以使用一个简单的程序生成数组,该程序以慢速方式逐位计数:

for (int i = 0 ; i != 256 ; i++) {
    int count = 0;
    for (int p = 0 ; p != 8 ; p++) {
        if (i & (1 << p)) {
            count++;
        }
    }
    printf("%d, ", count);
}

生成表的演示 )。

如果您希望将某些CPU周期换为内存,则可以将一个16字节的查找表用于两个4位查找:

static const char split_lookup[] = {
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4
};

int bit_count(unsigned char n) {
    return split_lookup[n&0xF] + split_lookup[n>>4];
}

演示

计算不为0的数字位数也称为汉明权重 在这种情况下,您要计算1的数量。

Dasblinkenlight为您提供了表驱动的实现,而Olaf为您提供了基于软件的解决方案。 我认为您还有另外两个潜在的解决方案。 第一种是使用编译器扩展,第二种是使用ASM特定指令以及C语言中的内联汇编。

对于第一种选择,请参阅GCC的__builtin_popcount() (感谢无误的噪音)。

对于第二种选择,您没有指定嵌入式处理器,但是在基于ARM的情况下,我将提供它。

某些ARM处理器具有VCNT指令,该指令将为您执行计数。 因此,您可以使用内联汇编从C中完成此操作:

inline
unsigned int hamming_weight(unsigned char value) {
    __asm__ __volatile__ (
            "VCNT.8"
            : "=value"
            : "value"
    );

    return value;
}

另请参阅计算寄存器ARM汇编中1最快的方法


为了完整起见,这是Kernighan的位计数算法:

int count_bits(int n) {
    int count = 0;
    while(n != 0) {
        n &= (n-1);
        count++;
    }
    return count;
}

另请参阅请解释Kernighan的位计数算法背后的逻辑

我认为您正在寻找8位汉明加权算法? 如果是这样,则代码如下:

unsigned char in = 22; //This is your input number
unsigned char out = 0;
in = in - ((in>>1) & 0x55);
in = (in & 0x33) + ((in>>2) & 0x33);
out = ((in + (in>>4) & 0x0F) * 0x01) ;

我做了一个优化的版本。 对于 32 位处理器,利用乘法、位移和掩码可以为相同的任务生成更小的代码,尤其是当输入域很小(8 位无符号整数)时。

以下两个代码片段是等效的:

unsigned int bit_count_uint8(uint8_t x)
{
    uint32_t n;
    n = (uint32_t)(x * 0x08040201UL);
    n = (uint32_t)(((n >> 3) & 0x11111111UL) * 0x11111111UL);
    return (n >> 28) & 0x0F;
}

/*
unsigned int bit_count_uint8_naive(uint8_t x)
{
    x = x - ((x >> 1) & 0x55);
    x = (x & 0x33) + ((x >> 2) & 0x33);
    x = ((x + (x >> 4)) & 0x0F);
    return x;
}
*/
  • 据我所知,这会为 IA-32、x86-64 和 AArch32(没有 NEON 指令集)生成最小的二进制代码。
  • 对于 x86-64,这并没有使用最少数量的指令,但是位移和向下转换避免了使用 64 位指令,因此在编译后的二进制文件中节省了一些字节。

解释

我将字节x的八位,从 MSB 到 LSB 表示为abcdefgh

                               abcdefgh
*   00001000 00000100 00000010 00000001 (make 4 copies of x
---------------------------------------  with appropriate
abc defgh0ab cdefgh0a bcdefgh0 abcdefgh  bit spacing)
>> 3                                   
---------------------------------------
    000defgh 0abcdefg h0abcdef gh0abcde
&   00010001 00010001 00010001 00010001
---------------------------------------
    000d000h 000c000g 000b000f 000a000e
*   00010001 00010001 00010001 00010001
---------------------------------------
    000d000h 000c000g 000b000f 000a000e
... 000h000c 000g000b 000f000a 000e
... 000c000g 000b000f 000a000e
... 000g000b 000f000a 000e
... 000b000f 000a000e
... 000f000a 000e
... 000a000e
... 000e
    ^^^^ (Bits 31-28 will contain the sum of the bits
          a, b, c, d, e, f, g and h. Extract these
          bits and we are done.)

也许不是最快,但很简单:

int count = 0;

for (int i = 0; i < 8; ++i) {
    unsigned char c = 1 << i;
    if (yourVar & c) {
        //bit n°i is set
        //first bit is bit n°0
        count++;
    }
}

对于8/16位MCU,循环很可能比并行加法更快,因为这些MCU每条指令的移位不能超过一位,因此:

size_t popcount(uint8_t val)
{
    size_t cnt = 0;
    do {
        cnt += val & 1U;    // or: if ( val & 1 ) cnt++;
    } while ( val >>= 1 ) ;
    return cnt;
}

为了增加cnt,您可以进行概要分析。 如果仍然太慢,可以使用进位标志(如果有)尝试使用assmber实现。 虽然我通常反对使用汇编程序优化,但是这样的算法是为数不多的好例外之一(仍然在C版本失败之后)。

如果可以省略Flash,则@dasblinkenlight建议的查找表可能是最快的方法。

只是一个提示:对于某些体系结构(尤其是ARM和x86 / 64),gcc具有内置函数:__ __builtin_popcount() ,您可能还想尝试一下是否可用(尽管它至少需要int)。 这可能只使用一条CPU指令-您无法获得更快,更紧凑的结果。

请允许我发布第二个答案。 这对于具有高级 SIMD 扩展 (NEON) 的 ARM 处理器来说是最小的。 它甚至比__builtin_popcount()更小(因为__builtin_popcount()针对unsigned int输入进行了优化,而不是uint8_t )。

#ifdef __ARM_NEON
/* ARM C Language Extensions (ACLE) recommends us to check __ARM_NEON before
   including <arm_neon.h> */
#include <arm_neon.h>

unsigned int bit_count_uint8(uint8_t x)
{
    /* Set all lanes at once so that the compiler won't emit instruction to
       zero-initialize other lanes. */
    uint8x8_t v = vdup_n_u8(x);
    /* Count the number of set bits for each lane (8-bit) in the vector. */
    v = vcnt_u8(v);
    /* Get lane 0 and discard other lanes. */
    return vget_lane_u8(v, 0);
}
#endif

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM