Выполнение перестановки битов на самом деле не требует реверсирования битов каких-либо целых чисел (это, конечно, способ реализовать его, но не лучший вариант). Это связано с тем, что алгоритм, выполняющий фактическую перестановку, не запрашивает произвольные целые числа с обратным битом в произвольном порядке. Было бы хорошо иметь эту последовательность (для n = 4)
0000
1000
0100
1100
0010
1010
...
Другой прием для генерации этой последовательности использует то, что операция i + 1
переносит младшие значащие установленные биты, делая их все равными нулю, и затем он устанавливает наименее значимый неустановленный бит. Или, другими словами, это похоже на операцию «инвертировать биты, начиная с LSB, но останавливаться после инвертирования первого нуля». Эту операцию можно относительно легко перевернуть битами, нам просто нужно XOR с помощью некоторой маски смежных установленных битов, длина которой может быть найдена путем вычисления __builtin_ctz(i + 1) + 1
(последний +1
должен включать ноль, который изменился на один на счету). Тогда сама маска может быть найдена как N - (N >> maskLength)
, где N
- это размер массива (степень двойки, вычитая сдвинутую версию, устанавливает все биты, начиная с этой более низкой позиции до более высокой позиции).
Например: (не проверено)
for (uint32_t i = 0, rev = 0; i < N; ++i)
{
if (i < rev)
swap(X[i], X[rev]);
int maskLen = __builtin_ctz(i + 1) + 1;
rev ^= N - (N >> maskLen);
}
__builtin_ctz
для G CC и Clang, для MSV C вы можете использовать _BitScanForward
(работает немного по-другому ).
Существует аналогичная уловка, которая использует начальный нулевой счет i ^ (i + 1)
.
Кстати, если это используется как часть БПФ, вы могли бы рассмотреть возможность использования алгоритмов, которые в этом нет необходимости, например, алгоритм Кули-Тьюки естественного порядка или алгоритм автосортировки Стокхема.
Фактическое обращение произвольного n
-битного числа может быть выполнено первым полностью переворачивая и затем сдвигая вправо на 32 - bits
(или 64, если было выполнено 64-битное обратное преобразование). Для каждого конкретного n
также существует соответствующий особый случай манипуляции с битами, но затем этот n
запекается в коде как константа. Использование полного реверса с последующим сдвигом работает для переменной n
. Некоторые процессоры могут иметь инструкции, которые помогают с этим, например, ARM (фактически ARMv6T2 и выше) имеет RBIT
.