【问题标题】:Vertical bitwise COUNT (sum of ones on the same position)垂直按位计数(同一位置的总和)
【发布时间】:2023-03-27 13:45:01
【问题描述】:

是否有任何有效的方法可以在许多变量中的同一位置上进行 COUNT 计数?计数函数应该用相应位数中的个数之和填充数组。例如我们有以下三个变量(为了简单起见,我使用 8 位变量):

uint8_t a = 0xF;  // 0000 1111
uint8_t b = 0x3C; // 0011 1100
uint8_t c = 0xF0; // 1111 0000

int result[8];

// some operations ...

count << result[0] << result[1] << .... // prints 1122 2211

我找到了许多解决方案,可以将整个单个变量的结果相加,但不适用于上述问题。

【问题讨论】:

  • 您需要 N 个变量的解决方案还是只需要 3 个?
  • 256 个查找表,其中包含要添加到累加器中的 8 0/1 字节数组?
  • 我认为 8*N 操作是你能得到的最好的(最多一个 const)..
  • 如果按照@MartinJames 的建议使用查找表,则将其减少到N。无论如何,这里没有神奇的解决方案。
  • 高效”在什么方面?速度、简单性、代码大小……?

标签: c++ c algorithm bit-manipulation


【解决方案1】:

这个小代码完全符合您的要求。您可以通过一个小的查找数组轻松扩展它以支持 N 个变量。注意使用双非操作。就是把输出拖到0或者1。

#include <iostream>

using namespace std;

int main() {
    uint8_t a = 0xF;  // 0000 1111
    uint8_t b = 0x3C; // 0011 1100
    uint8_t c = 0xF0; // 1111 0000

    unsigned result[8];
    for(int i = 0; i < 8; ++i) {
        unsigned mask = 1 << i;
        result[i] = !!(a & mask) + !!(b & mask) + !!(c & mask);
    }

    for(int i = 0; i < 8; ++i)
        cout << result[i];
}

【讨论】:

  • 我建议在计算结果后立即写出结果。这将消除另一个for-loop,并且您不需要数组result
  • 我假设他想将单个结果存储在一个地方。可以很容易地更改它以完全消除数组并在计算结果时输出结果,就像你说的那样。
  • 谢谢,这就是我要找的。是否有任何 SIMD 操作可以并行执行此类操作?
  • @nosbor 如果您可以访问 SIMD,那么这会简单得多。您基本上将位模式吹成一个宽向量(将 8 位输入映射到 64 位 8bitx8 向量,输入的每一位都分配给向量元素的最低位),然后对向量求和。
【解决方案2】:

将每个 uint8_t 二进制数字扩展为 uint32_t 十六进制数字并“将它们相加”。只要不超过每比特 15 个就好了。

#include <stdio.h>
#include <stdint.h>

// See below for tighter code
uint32_t shift_nibble(uint8_t x) {
  uint32_t y = 0;
  uint32_t mask = 1;
  while (x) {
    if (x & 1) {
      y |= mask;
    }
    mask <<= 4;
    x >>= 1;
  }
  return y;
}

void PrintVerticalBitwiseCount(const  uint8_t *x, size_t size) {
  uint32_t y = 0;
  for (size_t i=0; i<size; i++) {
    y += shift_nibble(x[i]);
  }
  printf("%08lX\n", (unsigned long) y);
}

int main(void) {
  const uint8_t a[] = { 0xF, 0x3C,  0xF0 };
  PrintVerticalBitwiseCount(a, sizeof a/sizeof *a);
  return 0;
}

输出

11222211

更快的候选人shift_nibble()。戴上你的八进制帽子

uint32_t shift_nibble(uint8_t x) {
  uint32_t y;
  y  = UINT32_C(0x01010101) & (UINT32_C(0001010101) * (x & 0x55));
  y |= UINT32_C(0x10101010) & (UINT32_C(0010101010) * (x & 0xAA));
  return y;
}

【讨论】:

  • 谢谢,这可能是这里最快的解决方案,唯一的限制是 15 是问题。
  • @nosbor 像往常一样,对问题的约束越严格,速度/大小/内存的目标就越好。可以使用uint64_t 并可能达到 255。但是您的“prints 1122 2211”暗示 15 就足够了。
【解决方案3】:

作为模板,我建议使用 C++11 中的以下函数。返回的列表在每个位的适当位置都有位计数,也就是说,最低有效位计数在位置 0,其次是位置 1,依此类推。 希望这对某人有所帮助。

template<typename T>
std::list<long>
vertical_bit_sum(std::vector<T> items)
{
    size_t bits = sizeof(T) * 8;
    std::list<long> result;
    do
    {
        long count = 0;
        for ( T item : items)
        {
            count += (0x1 & (item >> (bits-1)));
        }

        result.push_front (count);
        --bits;
    }
    while( bits > 0);

    return result;
}

std::list<long> result= vertical_bit_sum<uint8_t>( { 0xF, 0x3C, 0xF0  });

【讨论】:

    【解决方案4】:

    做这样的事情:

    uint64_t accum = 0;
    uint64_t table[0x100] = {.. precomputed vals ...};
    int count = 0xFF;
    while(bytes_available()) {
      if(--count == 0) {
        count = 0xFF;
        for(int i = 0; i < 8; i++)
          result[i] = ((uint8_t*)&accum)[i];
        accum = 0;
      }
      accum += table[(uint8_t)get_next_byte()];
    }
    

    【讨论】:

      【解决方案5】:

      为了提高速度,您可以通过将 8 个字节打包在一个 64 位累加器中来并行处理 8 个计数。

      用 256 个 64 位条目初始化一个查找表,这些条目是原来的 8 位扩展为字节。 (例如,条目Lookup[0x17u] 映射到0x0000000100010101ul。)

      计数只是用

      Acc+= Lookup[Byte];
      

      您可以通过在 64 位上映射一个 8 字节数组来检索各个计数器。

      在溢出之前可以进行256次累加;如果您需要更多,以 256 个块为单位进行累加,处理完一个块后,将计数转移到更大的累加器。

      如果你需要累加不超过 16 次,32 位累加器就足够了。

      【讨论】:

        【解决方案6】:

        确定变量中特定位置是否为 1 比进行正常人口计数要简单得多。

        bool hasOneAtPosition(int position, int target) {
            int mask = 1 << position;
            return (target & mask) != 0;
        }
        

        ... 只需在所有输入上调用它,并在每次返回 true 时增加一个计数器。简单的。

        【讨论】:

        • ... 很确定。它上面写着“答案”和一切。如果您认为我遗漏了部分问题,不妨告诉我您认为我遗漏了什么,而不是留下无益的评论?
        • "上面写着“答案”..." 有吗?不适合我。
        • 好吧,“答案”:P
        • 但它没有回答原始问题。它只是提供了一个函数,该函数可以返回是否在 int 中设置了一个位。它没有解决计算一组变量中设置了多少位的有效方法。当然,为每个变量引入一个函数调用似乎效率不高。
        • 使用unsigned 避免了移入符号位的UB问题。简体:bool hasOneAtPosition(int position, unsigned target) { return (1U &lt;&lt; position) &amp; target; }
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多