【问题标题】:C code to count the number of '1' bits in an unsigned char用于计算无符号字符中“1”位数的 C 代码
【发布时间】:2016-03-16 03:38:14
【问题描述】:

我需要 C 代码来返回 C 中 unsigned char 中 1 的数量。如果不明显,我需要解释为什么它可以工作。我发现了很多 32 位数字的代码,但对于 unsigned char 的代码却不多。

【问题讨论】:

  • 您的意思是您需要获取 unsigned char 值中的一位数吗?

标签: c hammingweight


【解决方案1】:
const unsigned char oneBits[] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};

unsigned char CountOnes(unsigned char x)
{
    unsigned char results;
    results = oneBits[x&0x0f];
    results += oneBits[x>>4];
    return results
}

有一个数组知道 0 到 15 的位数。将每个半字节的结果相加。

【讨论】:

  • 不错。我这样做的方式相同,但保存了除法和模数。
【解决方案2】:

同样的代码也适用于无符号字符。循环测试它们的所有位。见this

【讨论】:

    【解决方案3】:

    HACKMEM 在 3 次操作中有这个算法(大致翻译成 C):

    bits = (c * 01001001001ULL & 042104210421ULL) % 017;
    

    ULL 是强制 64 位算术。需要,只是勉强......这个计算需要 33 位整数。)

    实际上,您可以将第二个常量替换为 042104210021ULL,因为您只计算 8 位,但它看起来不太对称。

    这是如何工作的?按位考虑c,并记住(a + b) % c = (a % c + b % c) % c(a | b) == a + b iff (a & b) == 0

      (c * 01001001001ULL & 042104210421ULL) % 017
      01   01001001001                01         1
      02   02002002002       02000000000         1
      04   04004004004          04000000         1
     010  010010010010            010000         1
     020  020020020020               020         1
     040  040040040040      040000000000         1  # 040000000000 == 2 ** 32
    0100 0100100100100        0100000000         1
    0200 0200200200200           0200000         1
    

    如果您没有可用的 64 位算术,您可以将 c 拆分为半字节并执行每一半,进行 9 次操作。这仅需要 13 位,因此使用 16 位或 32 位算术即可。

    bits = ((c & 017) * 0421 & 0111) % 7 + ((c >> 4) * 0421 & 0111) % 7;
    
    (c * 0421 & 01111) % 7
     1   0421      01    1
     2  01042   01000    1
     4  02104    0100    1
     8  04210     010    1
    

    例如,如果c == 105 == 0b11001001

    c == 0100
       |  040
       |  010
       |   01 == 0151
    * 01001001001001ULL == 0100100100100
                         |  040040040040
                         |  010010010010
                         |   01001001001 == 0151151151151
    & 0421042104210421ULL ==  0100000000
                           | 04000000000
                           |      010000
                           |          01 ==   04100010001
    % 017                                == 4
    
    c & 017      ==            8 | 1           ==                   011
    011 * 0421   ==     8 * 0421 | 1 * 0421    == 04210 | 0421 == 04631
    04631 & 0111 == 04210 & 0111 | 0421 & 0111 ==   010 | 01   ==   011
    011 % 7      == 2
    
    c >> 4       ==            4 | 2            ==                     06
    06 * 0421    ==     4 * 0421 | 2 * 0421     == 02104 | 01042 == 03146
    03146 & 0111 == 02104 & 0111 | 01042 & 0111 ==  0100 | 01000 == 01100
    01100 % 7    == 2
    
    2 + 2 == 4
    

    【讨论】:

    • 在 0x77 上运行算法会得到什么?
    • @AndreArtus 你得到 6 个。
    【解决方案4】:

    请参阅 bit twiddling hacks 页面:http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan

    有很多很好的解决方案。

    此外,这个函数在其最简单的实现中是相当简单的。您应该花时间学习如何做到这一点。

    【讨论】:

      【解决方案5】:

      对于像 unsigned char 这样小的整数,使用小型查找表可以获得最佳性能。

      我知道您提到的人口计数算法。它们通过对小于寄存器中存储的整数的多个字进行算术运算来工作。

      这种技术称为 SWAR (http://en.wikipedia.org/wiki/SWAR)。

      有关更多信息,我建议您查看黑客喜悦网站:www.hackersdelight.org。他有示例代码并写了一本书详细解释了这些技巧。

      【讨论】:

        【解决方案6】:

        正如已经回答的那样,计数位的标准方法也适用于无符号字符。

        例子:

            unsigned char value = 91;
        int bitCount = 0;
        while(value > 0)
        {
            if ( value & 1 == 1 ) 
                bitCount++;
            value >>= 1;
        }
        

        【讨论】:

        • 这很糟糕。 1) 不必要的检查(值 > 0)。而(价值)通常会更好。这在 x86 性能方面并不重要,但可能在其他架构上。 2)内部循环中的分支是不必要的而且非常糟糕,bitCount += value & 1 更好。 3) 迭代所有 8 位而不是直到结果为 0 可能会展开循环并在大多数常见架构上编译成 8 对 shr、adc。当然,只使用 LUT 会更好。
        【解决方案7】:

        unsigned char 是一个“数字”,就像 32 位浮点数或整数是一个“数字”一样,编译器认为它们代表的是变化。

        如果你把一个字符想象成它的位:

        01010011(8 位);

        您可以通过执行以下操作来计算设置的位数:

        取值,假设 x,取 x % 2,余数将是 1 或 0。也就是说,取决于 char 的字节序,最左边或最右边的位。将余数累积到一个单独的变量中(这将是结果的设置位数)。

        然后>>(右移)1位。

        重复直到移动了 8 位。

        用我的伪代码实现 c 代码应该很简单,但基本上

        public static int CountSetBits(char c)
        {
            int x = 0;
            int setBits = 0;
            while (x < 7)
            {
               setBits = setBits + c % 2;
               c = c >> 1;
               x = x + 1;
            }
        }
        

        【讨论】:

          【解决方案8】:

          根据 Ephemient 的帖子,我们有没有分支的 8 位版本。它是十六进制表达式。

          typedef unsigned char       UINT8;
          typedef unsigned short      UINT16;
          typedef unsigned long long  UINT64;
          int hammingWeight8( const UINT8& c)
          {
              return ( c* 0x8040201ULL & 0x11111111)%0xF;
          }
          

          应用两次,我们有一个 16bits 的版本,需要 9 次操作。

          int hammingWeight16( const UINT16& c)
          {
              return ((c & 0xFF)* 0x8040201ULL & 0x11111111)%0xF + 
                       ((c >> 8)* 0x8040201ULL & 0x11111111)%0xF;
          }
          

          这里我写了一个 16 位版本的变体,它需要 64 位寄存器和 11 个操作。好像不比上一个好,不过就是用了1个模运算。

          int hammingWeight16( const UINT16& c)
          {
              UINT64  w;
              w= (((( c* 0x8000400020001ULL)>> 3) & 0x1111111111111111)+14)%0xF;
              return (c!=0)*(w+1+(c==0xFFFF)*15);
          }
          

          【讨论】:

            猜你喜欢
            • 2018-04-16
            • 1970-01-01
            • 2019-02-21
            • 2020-06-09
            • 1970-01-01
            • 2012-04-15
            • 2013-05-23
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多