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

Posted

技术标签:

【中文标题】将 n 位从 8 位数组复制到 64 位整数?【英文标题】:Copy n Bits from 8-bit Array into 64-bit Integer? 【发布时间】:2018-01-17 21:28:49 【问题描述】:

我正在尝试将 n 位从 uint8_ts 数组的任何位置复制到单个 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);

我认为没有人会这么快回答,所以这是我以相同风格提出的解决方案。我在 *** 上找到了这个 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;

【问题讨论】:

std::bitset ? 你考虑过使用std::bitset吗?它有std::bitset::to_ullong() 将位转换为unsigned long long(至少64 位的无符号整数类型)。 【参考方案1】:

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

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

【讨论】:

我在数组 255, 0, 255 上从位置 2 用 15 位进行了测试,得到的值是 111111 而不是 1111110000000 我可能搞砸了字节序,让我来解决这个问题 @SamG 已修复,从代码中扣除字节顺序并不简单 虽然你需要做更多的测试,如果你提供一些测试数据我可以添加到ideone【参考方案2】:

您的基本方法看起来不错。要处理不是 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++ 特定功能。请随意添加 std:: 来“C++ify”它,其中合适。)

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

【讨论】:

【参考方案3】:

我有以下

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);

【讨论】:

以上是关于将 n 位从 8 位数组复制到 64 位整数?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中将最低有效位从 int 复制到 long

将 16 位复制到内存位置

如何将 3 位整数格式化为 4 位字符串? [复制]

我怎样才能从一个位数组到一个字节? [复制]

将位间隔从变量复制到数组

NASM ctypes SIMD - 如何访问返回到ctypes的128位数组?