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

Copy n Bits from 8-bit Array into 64-bit Integer?

本文关键字:整数 复制 数组 位从      更新时间:2023-10-16

我正在尝试将 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);

我不认为有人会这么快回答,所以这是我以同样风格提出的解决方案。我在stackoverflow上找到了这个位字段掩码函数,但我找不到要归功于作者的问题。

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::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;
}

现场示例

您的基本方法看起来很合理。 要处理不是 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;
}

这是一个带有简单测试工具的在线演示,演示了此代码在我能找到的所有边缘情况下确实可以正常工作,例如从字节中间开始读取完整的 64 位或仅读取单个字节的一部分(这实际上是一个不平凡的特殊情况,在上面的代码中具有自己的return语句的单独分支中处理)。

(请注意,我用纯 C 编写了上面的代码,因为就像您的原始代码一样,它并没有真正使用任何C++特定功能。 随意通过在适当的地方添加std::来"C++化"它。

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

我有以下内容

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