У меня реализована функция, которая меняет местами биты в числе. Количество битов в инвертируемом числе может варьироваться. Однако, конечно, его необходимо сдвинуть влево в конце, чтобы выровнять с полубайтом, если он не делится на 4. Вот что у меня есть:
uint64_t revBits(uint64_t num)
{
unsigned int counter = 0;
uint64_t reverse_num = 0;
while (num)
{
reverse_num <<= 1;
reverse_num |= num & 1;
num >>= 1;
counter++;
}
if (counter % 4 != 0)
{
reverse_num <<= (4 - counter % 4);
}
return reverse_num;
}
Да, это работает. Однако есть ли более чистый способ сделать это в конце вместо проверки (counter % 4 != 0)
? Как однострочное решение для сдвига на основе числа % 4 без предварительной проверки, равно ли оно 0?
@pmg Я не уверен, что понимаю тебя
Я дал полный ответ из этого комментария :)
Почему функция, реверсирующая биты, должна соответствовать размеру полубайта?
Реверс группами по 4 бита?
Что-то вроде:
#include <inttypes.h>
#include <stdio.h>
uint64_t revBits(uint64_t num) {
unsigned int counter = 0;
uint64_t reverse_num = 0;
while (num) {
reverse_num <<= 1;
reverse_num |= num & 1;
num >>= 1;
counter++;
}
if (counter % 4 != 0) {
reverse_num <<= (4 - counter % 4);
}
return reverse_num;
}
uint64_t revBits4(uint64_t num) {
static const unsigned char reverse_table[] = {
0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0110, 0b1110,
0b0001, 0b1001, 0b0101, 0b1101, 0b0011, 0b1011, 0b0111, 0b1111};
uint64_t reverse_num = 0;
while (num) {
reverse_num <<= 4;
reverse_num |= reverse_table[num & 0b1111];
num >>= 4;
}
return reverse_num;
}
int main(void) {
for (uint64_t k = 0; k < 2000000000; k++) {
uint64_t v1, v2;
v1 = revBits(k);
v2 = revBits4(k);
if (v1 != v2) printf("Oops ... revBits4() failed for %" PRIu64 "\n", k);
}
}
Спасибо. На самом деле это отличное решение.
Заменять
if (counter % 4 != 0)
{
reverse_num <<= (4 - counter % 4);
}
с
reverse_num <<= ((-counter) % 4);
или
reverse_num <<= ((-counter) & 3);
При желании каждую пару скобок можно опустить.
Кроме того, можно уменьшить counter
вместо увеличения, тогда (-counter)
можно заменить на counter
в конце.
Будьте осторожны, если counter
имеет беззнаковый тип, уже, чем int
(например, если counter
имеет типы unsigned short
и USHRT_MAX <= INT_MAX
). В этом случае (-counter)
будет нулевым или отрицательным, и, следовательно, (-counter) % 4
будет нулевым или отрицательным, а операция сдвига с отрицательной величиной сдвига приведет к неопределенному поведению. Это неприменимо в случае ОП, потому что counter
имеет тип unsigned int
. Замена 4
на 4U
позволит избежать проблемы, поскольку отрицательное значение (-counter)
будет преобразовано в unsigned int
перед операцией по модулю, создавая нулевое или положительное значение сдвига. Точно так же & 3
можно заменить на & 3U
, чтобы избежать проблем, если int
не использует представление отрицательных значений с двоичным дополнением, хотя кажется маловероятным, что такие реализации когда-либо существовали, поскольку C23 требует представления с двоичным дополнением целочисленных типов со знаком.
@interjay Только для беззнаковых типов, уже int
. В этом случае 4
можно заменить на 4U
.
Вы правы, я пропустил unsigned
.
@IanAbbott Спасибо. До сих пор я никогда не рассматривал возможность использования модуля отрицательного числа. Это хорошо.
Реверс группами по 4 бита?
unsigned reverse_table[] = {0b0000, 0b1000, 0b0100, ..., 0b1111};