Доступ к 8-битным данным как к 7-битным

У меня массив 100 uint8_t‘s, который должен рассматриваться как поток 800 битов и обрабатываться 7 битами за раз. Другими словами, если первый элемент 8-битного массива содержит 0b11001100 а второй держит ob11110000 тогда, когда я приду, чтобы прочитать его в 7-битном формате, первый элемент 7-битного массива будет 0b1100110 а второй будет 0b0111100 с оставшимися 2 битами, удерживаемыми в третьем.
Первое, что я попробовал, был союз …

struct uint7_t {
uint8_t i1:7;
};

union uint7_8_t {
uint8_t u8[100];
uint7_t u7[115];
};

но, конечно, все выровнено по байту, и я, по сути, в итоге просто теряю 8-й бит каждого элемента.

У кого-нибудь есть идеи о том, как я могу это сделать?

Просто чтобы прояснить, это что-то вроде визуального представления результата объединения:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx 32 бита 8-битных данных
0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 32 бита 7-битных данных.

И это представляет то, что я хочу сделать вместо этого:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx 32 бита 8-битных данных
xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxx 32 бита 7-битных данных.

Я знаю, что последние биты могут быть дополнены, но это нормально, я просто хочу получить доступ к каждому биту по 7 бит за раз, не теряя ни одного из 800 бит. Пока что я могу думать только о том, как много сдвигать биты, что, конечно, сработает, но я уверен, что есть более чистый способ сделать это (?)

Заранее спасибо за любые ответы.

4

Решение

Не уверен, что вы подразумеваете под «чище». Обычно люди, которые работают над такой проблемой, регулярно считают смещение и маскировку правильным примитивным инструментом для использования. Можно сделать что-то вроде определения абстракции потока битов с помощью метода для считывания произвольного числа битов из потока. Эта абстракция иногда появляется в приложениях сжатия. Внутренние части метода, конечно, используют сдвиг и маскировку.

Один довольно чистый подход — написать функцию, которая извлекает 7-битное число с любым битовым индексом в массиве беззнаковых символов. Используйте деление для преобразования битового индекса в байтовый индекс и модуль для получения битового индекса в байте. Затем сдвиг и маска. Входные биты могут занимать два байта, поэтому вы должны либо склеить 16-битное значение перед извлечением, либо выполнить два меньших извлечения и / или их вместе, чтобы получить результат.

Если бы я стремился к чему-то умеренно производительному, я бы, вероятно, выбрал один из двух подходов:

Первая имеет две переменные состояния, указывающие, сколько бит нужно взять из текущего и следующего байта. Он будет использовать сдвиг, маскирование и побитовый или, чтобы получить текущий вывод (например, число от 0 до 127 в виде целого числа), затем цикл обновит обе переменные состояния с помощью сложения и модуля и увеличит текущие байтовые указатели если все биты в первом байте были использованы.

Второй подход заключается в загрузке 56-разрядных (8 входных значений) в 64-разрядное целое число и использование полностью развернутой структуры для извлечения каждого из 8 выходных данных. Выполнение этого без использования выравнивания чтения из памяти требует построения 64-битного целочисленного фрагмента. (56-бит является особенным, потому что начальная битовая позиция выровнена по байту.)

Чтобы по-настоящему быстро, я мог бы попробовать написать SIMD-код в Halide. Я считаю, что это выходит за рамки возможного. (И не ясно, на самом деле это много выиграет.)

Проекты, которые читают более одного байта в целое число за раз, вероятно, должны учитывать порядок байтов процессора.

2

Другие решения

Вот решение, которое использует специализацию вектора bool. Он также использует аналогичный механизм, чтобы разрешить доступ к семибитным элементам через ссылочные объекты.

Функции-члены допускают следующие операции:

uint7_t x{5};               // simple value
Arr<uint7_t> arr(10);       // array of size 10
arr[0] = x;                 // set element
uint7_t y = arr[0];         // get element
arr.push_back(uint7_t{9});  // add element
arr.push_back(x);           //
std::cout << "Array size is "<< arr.size() << '\n';  // get size
for(auto&& i : arr)
std::cout << i << '\n'; // range-for to read values
int z{50};
for(auto&& i : arr)
i = z++;                // range-for to change values
auto&& v = arr[1];          // get reference to second element
v = 99;                     // change second element via reference

Полная программа:

#include <vector>
#include <iterator>
#include <iostream>

struct uint7_t {
unsigned int i : 7;
};

struct seven_bit_ref {
size_t begin;
size_t end;
std::vector<bool>& bits;

seven_bit_ref& operator=(const uint7_t& right)
{
auto it{bits.begin()+begin};
for(int mask{1}; mask != 1 << 7; mask <<= 1)
*it++ = right.i & mask;
return *this;
}

operator uint7_t() const
{
uint7_t r{};
auto it{bits.begin() + begin};
for(int i{}; i < 7; ++i)
r.i += *it++ << i;
return r;
}

seven_bit_ref operator*()
{
return *this;
}

void operator++()
{
begin += 7;
end += 7;
}

bool operator!=(const seven_bit_ref& right)
{
return !(begin == right.begin && end == right.end);
}

seven_bit_ref operator=(int val)
{
uint7_t temp{};
temp.i = val;
operator=(temp);
return *this;
}

};

template<typename T>
class Arr;

template<>
class Arr<uint7_t> {
public:
Arr(size_t size) : bits(size * 7, false) {}

seven_bit_ref operator[](size_t index)
{
return {index * 7, index * 7 + 7, bits};
}
size_t size()
{
return bits.size() / 7;
}
void push_back(uint7_t val)
{
for(int mask{1}; mask != 1 << 7; mask <<= 1){
bits.push_back(val.i & mask);
}
}

seven_bit_ref begin()
{
return {0, 7, bits};
}

seven_bit_ref end()
{
return {size() * 7, size() * 7 + 7, bits};
}

std::vector<bool> bits;
};

std::ostream& operator<<(std::ostream& os, uint7_t val)
{
os << val.i;
return os;
}

int main()
{
uint7_t x{5};               // simple value
Arr<uint7_t> arr(10);       // array of size 10
arr[0] = x;                 // set element
uint7_t y = arr[0];         // get element
arr.push_back(uint7_t{9});  // add element
arr.push_back(x);           //
std::cout << "Array size is "<< arr.size() << '\n';  // get size
for(auto&& i : arr)
std::cout << i << '\n'; // range-for to read values
int z{50};
for(auto&& i : arr)
i = z++;                // range-for to change values
auto&& v = arr[1];          // get reference
v = 99;                     // change via reference
std::cout << "\nAfter changes:\n";
for(auto&& i : arr)
std::cout << i << '\n';
}
1

Обработайте их группами по 8 (так как 8×7 приятно округляет до чего-то 8-битного выровненного). Побитовые операторы — это порядок дня здесь. Разбираться с последними (до) 7 цифрами немного странно, но не невозможно. (Этот код предполагает, что это 7-разрядные целые числа без знака! Преобразование со знаком потребует от вас перевернуть верхний бит, если бит [6] равен 1)

// convert 8 x 7bit ints in one go
void extract8(const uint8_t input[7], uint8_t output[8])
{
output[0] =   input[0] & 0x7F;
output[1] =  (input[0] >> 7)  | ((input[1] << 1) & 0x7F);
output[2] =  (input[1] >> 6)  | ((input[2] << 2) & 0x7F);
output[3] =  (input[2] >> 5)  | ((input[3] << 3) & 0x7F);
output[4] =  (input[3] >> 4)  | ((input[4] << 4) & 0x7F);
output[5] =  (input[4] >> 3)  | ((input[5] << 5) & 0x7F);
output[6] =  (input[5] >> 2)  | ((input[6] << 6) & 0x7F);
output[7] =   input[6] >> 1;
}

// convert array of 7bit ints to 8bit
void seven_bit_to_8bit(const uint8_t* const input, uint8_t* const output, const size_t count)
{
size_t count8 = count >> 3;
for(size_t i = 0; i < count8; ++i)
{
extract8(input + 7 * i, output + 8 * i);
}

// handle remaining (upto) 7 bytes
const size_t countr = (count % 8);
if(countr)
{
// how many bytes do we need to copy from the input?
size_t remaining_bits = 7 * countr;
if(remaining_bits % 8)
{
// round to next nearest multiple of 8
remaining_bits += (8 - remaining_bits % 8);
}
remaining_bits /= 8;
{
uint8_t in[7] = {0}, out[8] = {0};
for(size_t i = 0; i < remaining_bits; ++i)
{
in[i] = input[count8 * 7 + i];
}
extract8(in, out);
for(size_t i = 0; i < countr; ++i)
{
output[count8 * 8 + i] = in[i];
}
}
}
}
1

Следующий код работает так, как вы его просили, но сначала вывод и живой пример на идеоне.

Выход:

Before changing values...:
7 bit representation: 1111111 0000000 0000000 0000000 0000000 0000000 0000000 0000000
8 bit representation: 11111110 00000000 00000000 00000000 00000000 00000000 00000000

After changing values...:
7 bit representation: 1000000 1001100 1110010 1011010 1010100 0000111 1111110 0000000
8 bit representation: 10000001 00110011 10010101 10101010 10000001 11111111 00000000

8 Bits: 11111111 to ulong: 255
7 Bits: 1111110 to ulong: 126

After changing values...:
7 bit representation: 0010000 0101010 0100000 0000000 0000000 0000000 0000000 0000000
8 bit representation: 00100000 10101001 00000000 00000000 00000000 00000000 00000000

Это очень просто, используя станд :: BitSet в классе под названием BitVector, Я реализую один геттер и сеттер. Получатель также возвращает std :: bitset по заданному индексу selIdx с заданным размером аргумента шаблона M, Данный idx будет умножен на данный размер M чтобы получить правильную позицию. Возвращенный набор битов также можно преобразовать в числовые или строковые значения.
Установщик использует значение uint8_t в качестве входных данных и снова индекс selIdx, Биты будут сдвинуты в правильную позицию в битовую группу.

Далее вы можете использовать метод получения и установки с различными размерами из-за аргумента шаблона MЭто означает, что вы можете работать как с 7- или 8-битным представлением, так и с 3-мя или чем угодно.

Я уверен, что этот код не самый лучший в отношении скорости, но я думаю, что это очень ясное и чистое решение. Кроме того, он не является полным, поскольку есть только один метод получения, один метод установки и два конструктора. Не забудьте реализовать проверку ошибок в отношении индексов и размеров.

Код:

#include <iostream>
#include <bitset>

template <size_t N> class BitVector
{
private:

std::bitset<N> _data;

public:

BitVector (unsigned long num) : _data (num) { };
BitVector (const std::string& str) : _data (str) { };

template <size_t M>
std::bitset<M> getBits (size_t selIdx)
{
std::bitset<M> retBitset;
for (size_t idx = 0; idx < M; ++idx)
{
retBitset |= (_data[M * selIdx + idx] << (M - 1 - idx));
}
return retBitset;
}

template <size_t M>
void setBits (size_t selIdx, uint8_t num)
{
const unsigned char* curByte = reinterpret_cast<const unsigned char*> (&num);
for (size_t bitIdx = 0; bitIdx < 8; ++bitIdx)
{
bool bitSet = (1 == ((*curByte & (1 << (8 - 1 - bitIdx))) >> (8 - 1 - bitIdx)));
_data.set(M * selIdx + bitIdx, bitSet);
}
}

void print_7_8()
{
std:: cout << "\n7 bit representation: ";
for (size_t idx = 0; idx < (N / 7); ++idx)
{
std::cout << getBits<7>(idx) << " ";
}
std:: cout << "\n8 bit representation: ";
for (size_t idx = 0; idx < N / 8; ++idx)
{
std::cout << getBits<8>(idx) << " ";
}
}
};

int main ()
{
BitVector<56> num = 127;

std::cout << "Before changing values...:";
num.print_7_8();

num.setBits<8>(0, 0x81);
num.setBits<8>(1, 0b00110011);
num.setBits<8>(2, 0b10010101);
num.setBits<8>(3, 0xAA);
num.setBits<8>(4, 0x81);
num.setBits<8>(5, 0xFF);
num.setBits<8>(6, 0x00);

std::cout << "\n\nAfter changing values...:";
num.print_7_8();

std::cout << "\n\n8 Bits: " << num.getBits<8>(5) << " to ulong: " << num.getBits<8>(5).to_ulong();
std::cout << "\n7 Bits: " << num.getBits<7>(6) << " to ulong: " << num.getBits<7>(6).to_ulong();

num = BitVector<56>(std::string("1001010100000100"));
std::cout << "\n\nAfter changing values...:";
num.print_7_8();

return 0;
}
1

Вот один подход без ручного переключения. Это просто грубый POC, но, надеюсь, вы сможете получить что-то из этого. Я не знаю, можете ли вы легко преобразовать свой вход в битовый набор, но я думаю, что это должно быть возможно.

int bytes = 0x01234567;
bitset<32> bs(bytes);
cout << "Input: " << bs << endl;
for(int i = 0; i < 5; i++)
{
bitset<7> slice(bs.to_string().substr(i*7, 7));
cout << slice << endl;
}

Кроме того, это, вероятно, намного менее производительно, чем версия с битшифтингом, поэтому я бы не рекомендовал ее для тяжелых работ.

0

Вы можете использовать это, чтобы получить index’th 7-битный элемент из in (обратите внимание, что он не имеет надлежащего конца обработки массива). Просто, быстро.

int get7(const uint8_t *in, int index) {
int fidx = index*7;
int idx = fidx>>3;
int sidx = fidx&7;

return (in[idx]>>sidx|in[idx+1]<<(8-sidx))&0x7f;
}
0

Вы можете использовать прямой доступ или массовую упаковку / распаковку, как в TurboPFor: целочисленное сжатие

// Direct read access
// b : bit width 0-16 (7 in your case)

#define bzhi32(u,b) ((u) & ((1u  <<(b))-1))

static inline unsigned  bitgetx16(unsigned char *in,
unsigned  idx,
unsigned b) {
unsigned bidx = b*idx;
return bzhi32( *(unsigned *)((uint16_t *)in+(bidx>>4)) >> (bidx& 0xf), b );
}
0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector