将n位从8位数组复制到64位整数?

时间:2018-01-17 21:28:49

标签: c++ algorithm bit-manipulation

我试图将uint8_ts数组的任何位置的n位复制到一个64位整数中。这是一个工作解决方案,可以将任意数量的位复制到从数组开头开始的64位整数,但我希望能够从数组的任何位置开始。

例如,我可能想要复制数组的第2位到第11位: {7,128,7}

在二进制文件中: 00000111 1000000 00000111

我想要一个有值的整数: 0001111000

std::uint64_t key_reg(std::uint8_t* bytes, std::size_t n)
{
  std::uint64_t reg = 0;
  // The amount of bits that fit into an entire element of an array
  // ex, if I'm copying 17 bits, even_bytes == 2
  std::size_t even_bytes = (n - (n % 8)) / 8;
  // what's left over after the even bytes
  // in this case, remainder == 1
  std::size_t remainder = n - even_bytes * 8;

  // copy each byte into the integer
  for(std::size_t i = 0; i < even_bytes; ++i)
    if(remainder)
      reg |= (std::uint64_t)bytes[i] << (8 * (even_bytes - i));
    else
      reg |= (std::uint64_t)bytes[i] << (8 * (even_bytes - i - 1));

  // if there is an uneven number of bits, copy them in
  if(remainder) 
    reg |= (std::uint64_t)bytes[even_bytes];

  return reg;
}

您是否知道如何实施

std::uint64_t key_reg(std::uint8_t* bytes, std::size_t pos, std::size_t n);

我没想到有人会这么快回答,所以这是我提出的同样风格的解决方案。我在stackoverflow上找到了这个bitfieldmask函数,但是我无法找到问题归功于作者。

template<typename R>
static constexpr R bitfieldmask(unsigned int const a, unsigned int const b)
{
  return ((static_cast<R>(-1) >> (((sizeof(R) * CHAR_BIT) - 1) - (b)))
      & ~((1 << (a)) - 1));  
}

std::uint64_t key_reg(std::uint8_t* bytes, std::size_t pos, std::size_t n)
{
  std::uint64_t reg = 0;
  std::size_t starting_byte = (pos < 8) ? 0 : ((pos - (pos % 8)) / 8);
  std::size_t even_bytes = (n - (n % 8)) / 8;
  std::size_t remainder = n - even_bytes * 8;

  for(std::size_t i = 0; i < even_bytes; ++i)
    if(remainder)
      reg |= (std::uint64_t)bytes[starting_byte + i] << (8 * (even_bytes - i));
    else
      reg |= (std::uint64_t)bytes[starting_byte + i] << (8 * (even_bytes - i - 1));

  if(remainder) 
    reg |= (std::uint64_t)bytes[even_bytes];

  // mask out anything before the first bit
  if(pos % 8 != 0) {
    std::size_t a = n - pos;
    std::size_t b = n;
    auto mask = bitfieldmask<std::uint64_t>(a, b);

    reg = (reg & ~mask);
  }

  return reg;
}

3 个答案:

答案 0 :(得分:2)

我认为复制所有必需的字节然后屏蔽额外的位更简单:

std::uint64_t key_reg(std::uint8_t* bytes, std::size_t n)
{
   std::uint64_t reg = 0;
   std::reverse_copy( bytes, bytes + n / 8 + ( n % 8 != 0 ), 
                      reinterpret_cast<char *>( &reg ) );
   reg >>= n % 8;
   reg &= ~( -1UL << n );
   return reg;
}

使用pos会更复杂一些:

std::uint64_t key_reg(std::uint8_t* bytes, std::size_t pos, std::size_t n)
{
   std::uint64_t reg = 0;
   auto endpos = pos + n;
   auto start = bytes + pos / 8;
   auto end = bytes + endpos / 8 + ( endpos % 8 != 0 );
   std::reverse_copy( start,  end, reinterpret_cast<char *>( &reg ) );
   reg >>= endpos % 8;
   reg &= ~( -1UL << n );
   return reg;
}

live example

答案 1 :(得分:0)

我有以下

struct MyType
{
std::array<uint8_t, 892> m_rguID;
    uint16_t m_bitLength;


void GetBits(uint16_t startBit, uint16_t nBits, uint64_t & bits) const
};


void MyType::GetBits(uint16_t startBit, uint16_t nBits, uint64_t & bits) const
{
    if(startBit + nBits > m_bitLength)
        throw std::runtime_error("Index is out of range");
    uint32_t num1 = startBit % 8U;
    uint32_t num2 = 8U - num1;
    uint32_t num3 = nBits >= num2 ? num2 : nBits;
    uint32_t num4 = startBit >> 3;
    bits = (uint64_t)(((int64_t)((uint64_t)m_rguID[num4] >> (8 - num3 - num1)) & (int64_t)((1 << num3) - 1)) << (nBits - num3));
    uint32_t num5 = num4 + 1U;
    int num6 = nBits - num3;
    if(num6 <= 0)
        return;
    int num7 = num6 - 8;
    int num8 = 8 - num6;
    do
    {
        if(num6 >= 8)
        {
            bits |= (uint64_t)m_rguID[num5] << num7;
            ++num5;
        }
        else
        {
            bits |= (uint64_t)m_rguID[num5] >> num8;
            ++num5;
        }
        num6 += -8;
        num7 += -8;
        num8 += 8;
    } while(num6 > 0);
}

答案 2 :(得分:0)

您的基本方法看起来很合理。要处理不是8的倍数的位偏移,您只需先读取一个部分字节,然后继续其余部分:

uint64_t key_reg(const uint8_t* bytes, size_t pos, size_t n) {
    const uint8_t* ptr = bytes + pos / 8;
    uint64_t result = 0;

    if (pos % 8 > 0) {
        /* read the first partial byte, masking off unwanted bits */
        result = *(ptr++) & (0xFF >> (pos % 8));

        if (n <= 8 - pos % 8) {
            /* we need no more bits; shift off any excess and return early */
            return result >> (8 - pos % 8 - n);
        } else {
            /* reduce the requested bit count by the number we got from this byte */
            n -= 8 - pos % 8;
        }
    }

    /* read and shift in as many whole bytes as we need */
    while (n >= 8) {
        result = (result << 8) + *(ptr++);
        n -= 8;
    }

    /* finally read and shift in the last partial byte */
    if (n > 0) {
        result = (result << n) + (*ptr >> (8-n));
    }
    return result;
}

Here's an online demo带有一个简单的测试工具,证明这个代码确实在我能找到的所有边缘情况下都能正常工作,例如从一个字节的中间开始读取一个完整的64位或只读取一部分的单字节(实际上是一个非平凡的特殊情况,在上面的代码中使用自己的return语句在一个单独的分支中处理。)

(请注意,我在普通C中编写了上面的代码,因为与原始代码一样,它并没有真正使用任何特定于C ++的功能。通过添加{{1}可以随意使用“C ++ ify”在适当的地方。)

测试工具没有检查但我相信这段代码应该拥有的一个特性是它从不会从输入数组中读取多于必要的字节。特别是,如果std::,则根本不访问bytes数组(尽管仍在计算数组开始后指向n == 0字节的指针)。