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

У меня есть массив из 100 uint8_t, который должен обрабатываться как поток из 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 бит. Пока единственный способ, о котором я могу думать, - это много смещения, которое, конечно же, будет работать, но я уверен, что есть более чистый способ обойти это (?)

Заранее благодарим за любые ответы.

3
задан 28 июня '17 в 21:49
источник поделиться
7 ответов

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

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

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

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

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

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

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

2
ответ дан 28 июня '17 в 22:30
источник

Вот решение, которое использует векторную специализацию 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
ответ дан 29 июня '17 в 0:07
источник

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

Вывод:

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 

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

Далее вы можете использовать getter и setter с разными размерами из-за аргумента шаблона 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
ответ дан 29 июня '17 в 21:15
источник

Обработайте их группами по 8 (так как 8x7 красиво округлены до 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];
      }
    }
  }
}
0
ответ дан 29 июня '17 в 6:06
источник

Вы можете использовать упаковку/распаковку прямого доступа или массовой упаковки, как в 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
ответ дан 30 июня '17 в 15:06
источник

Вот один подход без ручного переключения. Это всего лишь грубый 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
ответ дан 28 июня '17 в 22:18
источник

Вы можете использовать это, чтобы получить 7-битный элемент index из 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
ответ дан 28 июня '17 в 22:23
источник

Другие вопросы по меткам или Задайте вопрос