Обратный порядок бит в бит-массиве

У меня длинная последовательность бит, хранящаяся в массиве беззнаковых длинных целых чисел, вроде этого

struct bit_array
{
    int size; /* nr of bits */
    unsigned long *array; /* the container that stores bits */
}

Я пытаюсь создать алгоритм для изменения порядка бит в * массиве. Проблемы:

  • size может быть любым, то есть не обязательно кратным 8 или 32 и т.д., поэтому первый бит во входном массиве может оказаться в любой позиции в unsigned long в выходном массиве;
  • алгоритм должен быть независимым от платформы, т.е. работать для любого sizeof(unsigned long).

Код, псевдокод, описание алгоритма и т.д. - приветствуется что-либо лучше, чем метод bruteforce ( "побитно" ).

Ответы

Ответ 1

Мое любимое решение состоит в том, чтобы заполнить таблицу lookup-table, которая выполняет бит-обращение на одном байте (следовательно, записи в 256 байт).

Вы применяете таблицу к 1 - 4 байтам входного операнда с заменой. Если размер не кратен 8, вам нужно будет отрегулировать окончательную правую смену.

Это хорошо масштабируется для больших целых чисел.

Пример:

11 10010011 00001010 -> 01010000 11001001 11000000 -> 01 01000011 00100111

Чтобы разделить число на байты портативно, вам нужно использовать побитовое маскирование/сдвиги; отображение структуры или массива байтов на целое число может сделать его более эффективным.

Для грубой производительности вы можете думать о отображении до 16 бит за раз, но это не выглядит вполне разумным.

Ответ 2

Мне нравится идея таблицы поиска. Тем не менее это также типичная задача для логарифмических (n) групповых трюков, которые могут быть очень быстрыми. Как:

unsigned long reverseOne(unsigned long x) {
  x = ((x & 0xFFFFFFFF00000000) >> 32) | ((x & 0x00000000FFFFFFFF) << 32);
  x = ((x & 0xFFFF0000FFFF0000) >> 16) | ((x & 0x0000FFFF0000FFFF) << 16);
  x = ((x & 0xFF00FF00FF00FF00) >> 8)  | ((x & 0x00FF00FF00FF00FF) << 8);
  x = ((x & 0xF0F0F0F0F0F0F0F0) >> 4)  | ((x & 0x0F0F0F0F0F0F0F0F) << 4);
  x = ((x & 0xCCCCCCCCCCCCCCCC) >> 2)  | ((x & 0x3333333333333333) << 2);
  x = ((x & 0xAAAAAAAAAAAAAAAA) >> 1)  | ((x & 0x5555555555555555) << 1);
  return x;
}

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

Вот более портативная версия, поддерживающая ширину unsigned long 4,8,16 или 32 байта.

#include <limits.h>

#define ones32 0xFFFFFFFFUL
#if (ULONG_MAX >> 128)
#define fill32(x) (x|(x<<32)|(x<<64)|(x<<96)|(x<<128)|(x<<160)|(x<<192)|(x<<224))
#define patt128 (ones32|(ones32<<32)|(ones32<<64) |(ones32<<96))
#define patt64  (ones32|(ones32<<32)|(ones32<<128)|(ones32<<160))
#define patt32  (ones32|(ones32<<64)|(ones32<<128)|(ones32<<192))
#else
#if (ULONG_MAX >> 64)
#define fill32(x) (x|(x<<32)|(x<<64)|(x<<96))
#define patt64  (ones32|(ones32<<32))
#define patt32  (ones32|(ones32<<64))
#else
#if (ULONG_MAX >> 32)
#define fill32(x) (x|(x<<32))
#define patt32  (ones32)
#else
#define fill32(x) (x)
#endif
#endif
#endif

unsigned long reverseOne(unsigned long x) {
#if (ULONG_MAX >> 32)
#if (ULONG_MAX >> 64)
#if (ULONG_MAX >> 128)
  x = ((x & ~patt128) >> 128) | ((x & patt128) << 128);
#endif
  x = ((x & ~patt64) >> 64) | ((x & patt64) << 64);
#endif
  x = ((x & ~patt32) >> 32) | ((x & patt32) << 32);
#endif
  x = ((x & fill32(0xffff0000UL)) >> 16) | ((x & fill32(0x0000ffffUL)) << 16);
  x = ((x & fill32(0xff00ff00UL)) >> 8)  | ((x & fill32(0x00ff00ffUL)) << 8);
  x = ((x & fill32(0xf0f0f0f0UL)) >> 4)  | ((x & fill32(0x0f0f0f0fUL)) << 4);
  x = ((x & fill32(0xccccccccUL)) >> 2)  | ((x & fill32(0x33333333UL)) << 2);
  x = ((x & fill32(0xaaaaaaaaUL)) >> 1)  | ((x & fill32(0x55555555UL)) << 1);
  return x;
}

Ответ 3

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

unsigned int v;     // input bits to be reversed
unsigned int r = v; // r will be reversed bits of v; first get LSB of v
int s = sizeof(v) * CHAR_BIT - 1; // extra shift needed at end

for (v >>= 1; v; v >>= 1)
{   
  r <<= 1;
  r |= v & 1;
  s--;
}
r <<= s; // shift when v highest bits are zero

Реверсирование всего массива может быть выполнено впоследствии путем изменения отдельных позиций.

Ответ 4

Вы должны определить, каков порядок бит в unsigned long. Вы можете предположить, что бит n соответствует array[x] & (1 << n), но это необходимо указать. Если это так, вам нужно обработать порядок байтов (маленький или большой endian), если вы собираетесь использовать доступ к массиву как байты вместо unsigned long.

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

Ответ 5

Тот факт, что размер не кратен sizeof(long), является самой сложной частью проблемы. Это может привести к большому смещению бит.

Но вам не нужно это делать, если вы можете ввести новый член структуры:

struct bit_array
{
    int size; /* nr of bits */
    int offset; /* First bit position */
    unsigned long *array; /* the container that stores bits */
}

Смещение будет сообщать вам, сколько бит игнорировать в начале массива.

Тогда вам нужно сделать только следующие шаги:

  • Обратные элементы массива.
  • Сменить бит каждого элемента. В других ответах есть много хаков, но ваш компилятор может также обеспечить интригические функции, чтобы сделать это за меньшее количество инструкций (например, инструкция RBIT на некоторых ядрах ARM).
  • Рассчитать новое начальное смещение. Это равно неиспользуемым битам последнего элемента.

Ответ 6

Я бы разделил проблему на две части.

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

псевдокод:

for half the longs in the array:
    take the first longword;
    take the last longword;
    swap the bits in the first longword
    swap the bits in the last longword;

    store the swapped first longword into the last location;
    store the swapped last longword into the first location;

а затем исправить тот факт, что первые несколько бит (вызов, чем номер n), на самом деле являются битами для мусора с конца длин:

for all of the longs in the array:
    split the value in the leftmost n bits and the rest;
    store the leftmost n bits into the righthand part of the previous word;
    shift the rest bits to the left over n positions (making the rightmost n bits zero);
    store them back;

Вы могли бы попытаться сложить это в один проход по всему массиву, конечно. Что-то вроде этого:

for half the longs in the array:
    take the first longword;
    take the last longword;
    swap the bits in the first longword
    swap the bits in the last longword;

    split both value in the leftmost n bits and the rest;

    for the new first longword:
        store the leftmost n bits into the righthand side of the previous word;
        store the remaining bits into the first longword, shifted left;

    for the new last longword:
        remember the leftmost n bits for the next iteration;
        store the remembered leftmost n bits, combined with the remaining bits, into the last longword;

    store the swapped first longword into the last location;
    store the swapped last longword into the first location;

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