В C / C ++ каков самый простой способ изменить порядок бит в байте?


110

Хотя есть несколько способов изменить порядок следования битов в байтах, мне любопытно, что «проще всего» реализовать разработчику. Под реверсом я подразумеваю:

1110 -> 0111
0010 -> 0100

Это похоже, но не дублирует этот вопрос PHP.

Это похоже, но не дублирует этот вопрос C. Этот вопрос касается самого простого метода, который может реализовать разработчик. «Лучший алгоритм» касается памяти и производительности процессора.


Используйте встроенную сборку. Лучше вынести функцию в отдельную единицу перевода. Имейте по одному модулю языка ассемблера для каждой целевой платформы. Позвольте процессу сборки выбирать модули.
Томас Мэтьюз,

@Andreas Простейшая реализация
Натан

Ответы:


102

Если вы говорите об одном байте, поиск в таблице, вероятно, является лучшим вариантом, если по какой-либо причине у вас нет доступных 256 байтов.


12
Если мы говорим о чем-то, что легко реализовать без копирования готового решения, для создания таблицы поиска все же требуется другое решение. (Конечно, можно сделать это вручную, но это
чревато

7
Вы можете сжать массив до 256 байт, если игнорируете палиндромы.
Wilhelmtell

8
@wilhelmtell - вам понадобится таблица, чтобы знать, какие из них являются палиндромами.
Марк Рэнсом,

6
@wilhelmtell: Что ж, для написания сценария все еще нужно другое решение, на что я и обращал внимание - таблица поиска проста в использовании, но не проста в создании. (За исключением копирования готовой таблицы поиска, но тогда можно также скопировать любое решение.) Например, если «простейшее» решение считается тем, которое может быть записано на бумаге на экзамене или собеседовании, я бы не стал начните создавать таблицу поиска вручную и заставляя программу делать это, уже будет включать другое решение (которое было бы проще, чем то, которое включает и его, и таблицу).
Arkku

4
@Arkku я имел в виду написать сценарий, который выводит таблицу из первых 256 байтов и их обратное отображение. Да, вы вернулись к написанию обратной функции, но теперь на вашем любимом языке сценариев, и она может быть сколь угодно неприятной - вы выбросите ее, как только она будет выполнена, и вы запустили ее один раз. Есть вывод скрипта как код C, даже: unsigned int rtable[] = {0x800, 0x4000, ...};. Затем выбросьте сценарий и забудьте, что он у вас когда-либо был. Его писать намного быстрее, чем эквивалентный код C ++, и он будет запускаться только один раз, поэтому вы получаете время выполнения O (1) в вашем коде C ++.
Вильгельмтелл

227

Это должно работать:

unsigned char reverse(unsigned char b) {
   b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
   b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
   b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
   return b;
}

Сначала левые четыре бита меняются местами с правыми четырьмя битами. Затем меняются местами все соседние пары, а затем все смежные одиночные биты. Это приводит к обратному порядку.


26
Достаточно коротко и быстро, но не просто.
Марк Рэнсом,

3
Этот подход также чисто обобщает замену байтов для обеспечения порядка байтов.
Буджум

2
Не самый простой подход, но мне нравится +1.
Натан

8
Да, это просто. Это своего рода алгоритм «разделяй и властвуй». Превосходно!
kiewic

Это быстрее, чем метод, предложенный @Arkku ниже?
ответила 01

123

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

//Index 1==0b0001 => 0b1000
//Index 7==0b0111 => 0b1110
//etc
static unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };

uint8_t reverse(uint8_t n) {
   // Reverse the top and bottom nibble then swap them.
   return (lookup[n&0b1111] << 4) | lookup[n>>4];
}

// Detailed breakdown of the math
//  + lookup reverse of bottom nibble
//  |       + grab bottom nibble
//  |       |        + move bottom result into top nibble
//  |       |        |     + combine the bottom and top results 
//  |       |        |     | + lookup reverse of top nibble
//  |       |        |     | |       + grab top nibble
//  V       V        V     V V       V
// (lookup[n&0b1111] << 4) | lookup[n>>4]

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


10
Это отличный способ уменьшить сложность табличного решения. +1
e.James 08

3
Хорошо, но это даст вам промах кэширования.
Йохан Котлински

7
@kotlinski: что вызовет промах кеша? Я думаю, что версия с маленькой таблицей может быть более эффективной кеш-памяти, чем большая. На моем Core2 строка кэша имеет ширину 64 байта, полная таблица будет охватывать несколько строк, тогда как меньшая таблица легко помещается в одну строку.
deft_code 01

4
@kotlinski: временная локальность более важна для попаданий в кеш или стратегий замены, чем адресная локальность
cfi

6
@Harshdeep: рассмотрите двоично-закодированные индексы записей таблицы. индекс b0000 (0) -> b0000 (0x0) скучно; b0001(1) -> b1000(0x8), b0010(2) -> b0100(0x4), b1010(10) -> b0101(0x5). Видите узор? Это достаточно просто, чтобы вы могли вычислить это в уме (если вы умеете читать двоичный код, иначе вам понадобится бумага, чтобы решить это). Что касается скачка, то реверсирование 8-битного целого числа аналогично переворачиванию 4-битных частей и их замене местами; Я требую опыта и интуиции (или магии).
deft_code 02

46

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

Например (на 32-битном процессоре):

uint8_t b = byte_to_reverse;
b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;

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


1
Из вашего URL: 32-битный процессор: b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Джошуа

1
@Joshua: Это тоже мой личный фаворит. Предостережение (как указано на связанной странице) заключается в том, что его необходимо назначить или преобразовать в uint8_t, иначе в верхних битах будет мусор.
Arkku

42

Поскольку никто не опубликовал полное решение для поиска в таблице, вот мое:

unsigned char reverse_byte(unsigned char x)
{
    static const unsigned char table[] = {
        0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
        0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
        0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
        0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
        0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
        0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
        0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
        0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
        0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
        0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
        0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
        0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
        0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
        0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
        0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
        0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
        0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
        0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
        0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
        0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
        0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
        0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
        0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
        0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
        0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
        0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
        0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
        0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
        0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
        0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
        0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
        0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
    };
    return table[x];
}

2
Полезно, спасибо. Похоже, мой метод более медленного переключения ограничивал производительность во встроенном приложении. Помещенная таблица в ROM на PIC (с добавлением ключевого слова rom).
Flend


25
template <typename T>
T reverse(T n, size_t b = sizeof(T) * CHAR_BIT)
{
    assert(b <= std::numeric_limits<T>::digits);

    T rv = 0;

    for (size_t i = 0; i < b; ++i, n >>= 1) {
        rv = (rv << 1) | (n & 0x01);
    }

    return rv;
}

РЕДАКТИРОВАТЬ:

Преобразовал его в шаблон с необязательным битом


@nvl - исправлено. Я начал создавать его как шаблон, но на полпути решил не делать этого ... слишком много & gt & lt
andand

Для дополнительной педенатрии замените sizeof(T)*8на sizeof(T)*CHAR_BITS.
Pillsy 08

6
@andand Для дополнительной дополнительной подвески замените sizeof(T)*CHAR_BITна std::numeric_limits<T>::digits(спустя почти 4 года педантизма).
Morwenn

1
Должно быть CHAR_BIT, нет CHAR_BITS.
Xunie 08

1
это должно быть rv = (rv << 1) | (n & 0x01);
Вигнеш

16

Две строки:

for(i=0;i<8;i++)
     reversed |= ((original>>i) & 0b1)<<(7-i);

или если у вас есть проблемы с частью "0b1":

for(i=0;i<8;i++)
     reversed |= ((original>>i) & 1)<<(7-i);

"оригинал" - это байт, который нужно перевернуть. "reverse" - результат, инициализированный 0.


14

Я бы использовал ассемблер, хотя, вероятно, не переносимый.
На многих языках ассемблера есть инструкции, как немного повернуть флаг переноса и повернуть флаг переноса в слово (или байт).

Алгоритм такой:

for each bit in the data type:
  rotate bit into carry flag
  rotate carry flag into destination.
end-for

Код на языке высокого уровня для этого намного сложнее, потому что C и C ++ не поддерживают вращение для переноса и вращение для переноса. Флаг переноса должен быть смоделирован.

Изменить: например, язык ассемблера

;  Enter with value to reverse in R0.
;  Assume 8 bits per byte and byte is the native processor type.
   LODI, R2  8       ; Set up the bit counter
Loop:
   RRC, R0           ; Rotate R0 right into the carry bit.
   RLC, R1           ; Rotate R1 left, then append carry bit.
   DJNZ, R2  Loop    ; Decrement R2 and jump if non-zero to "loop"
   LODR, R0  R1      ; Move result into R0.

7
Я думаю, что этот ответ противоположен простому. Непереносимый, сборочный и достаточно сложный, чтобы его можно было написать псевдокодом вместо фактической сборки.
deft_code 08

3
Все очень просто. Я поместил это в псевдокод, потому что мнемоника ассемблера специфична для разных типов процессоров, а их много. Если хотите, я могу отредактировать это, чтобы показать простой язык ассемблера.
Томас Мэтьюз,

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

12

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

unsigned char reverse_byte(char a)
{

  return ((a & 0x1)  << 7) | ((a & 0x2)  << 5) |
         ((a & 0x4)  << 3) | ((a & 0x8)  << 1) |
         ((a & 0x10) >> 1) | ((a & 0x20) >> 3) |
         ((a & 0x40) >> 5) | ((a & 0x80) >> 7);
}

Он получает каждый бит байта и соответственно сдвигает его, начиная с первого до последнего.

Объяснение:

   ((a & 0x1) << 7) //get first bit on the right and shift it into the first left position 
 | ((a & 0x2) << 5) //add it to the second bit and shift it into the second left position
  //and so on

Прекрасный! Пока что мой любимый.
Ник Рамо,

Это, конечно, просто, но следует отметить, что время выполнения составляет O (n), а не O (log₂ n), где n - количество бит (8, 16, 32, 64 и т. Д.).
Тодд Леман,

10

Самый простой способ - это перебрать битовые позиции в цикле:

unsigned char reverse(unsigned char c) {
   int shift;
   unsigned char result = 0;
   for (shift = 0; shift < CHAR_BIT; shift++) {
      if (c & (0x01 << shift))
         result |= (0x80 >> shift);
   }
   return result;
}

это CHAR_BIT, без 's'
ljrk

Зачем использовать, CHAR_BITесли предполагается, charчто у вас 8 бит?
chqrlie

6

Вас могут заинтересовать std::vector<bool>(это битовая упаковка) иstd::bitset

По запросу он должен быть самым простым.

#include <iostream>
#include <bitset>
using namespace std;
int main() {
  bitset<8> bs = 5;
  bitset<8> rev;
  for(int ii=0; ii!= bs.size(); ++ii)
    rev[bs.size()-ii-1] = bs[ii];
  cerr << bs << " " << rev << endl;
}

Другие варианты могут быть быстрее.

РЕДАКТИРОВАТЬ: Я должен вам решение, используя std::vector<bool>

#include <algorithm>
#include <iterator>
#include <iostream>
#include <vector>
using namespace std;
int main() {
  vector<bool> b{0,0,0,0,0,1,0,1};
  reverse(b.begin(), b.end());
  copy(b.begin(), b.end(), ostream_iterator<int>(cerr));
  cerr << endl;
}

Во втором примере требуется расширение c ++ 0x (для инициализации массива {...}). Преимущество использования a bitsetили a std::vector<bool>(или a boost::dynamic_bitset) в том, что вы не ограничены байтами или словами, но можете изменить произвольное количество бит.

НТН


Чем здесь битсет проще, чем модуль? Покажи код, или нет.
Wilhelmtell

На самом деле, я думаю, что этот код полностью изменит битовый набор, а затем вернет его к исходному состоянию. Измените ii! = Size (); to ii <size () / 2; и он будет работать лучше =)
Виктор Сер

(@ viktor-sehr нет, не будет, rev отличается от bs). Во всяком случае, мне сам ответ не нравится: я думаю, что это тот случай, когда лучше подходят бинарная арифметика и операторы сдвига. Это все еще остается самым простым для понимания.
baol

Как насчет std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); прямого использования обратных итераторов?
MSalters

@MSalters Мне нравится его неизменность.
baol 01

6

Для очень ограниченного случая постоянного 8-битного ввода этот метод не требует затрат памяти или процессора во время выполнения:

#define MSB2LSB(b) (((b)&1?128:0)|((b)&2?64:0)|((b)&4?32:0)|((b)&8?16:0)|((b)&16?8:0)|((b)&32?4:0)|((b)&64?2:0)|((b)&128?1:0))

Я использовал это для ARINC-429, где битовый порядок (порядок следования байтов) метки противоположен остальной части слова. Метка часто является постоянной и обычно восьмеричной.

Вот как я использовал его для определения константы, потому что спецификация определяет эту метку как восьмеричное число с прямым порядком байтов 205.

#define LABEL_HF_COMM MSB2LSB(0205)

Еще примеры:

assert(0b00000000 == MSB2LSB(0b00000000));
assert(0b10000000 == MSB2LSB(0b00000001));
assert(0b11000000 == MSB2LSB(0b00000011));
assert(0b11100000 == MSB2LSB(0b00000111));
assert(0b11110000 == MSB2LSB(0b00001111));
assert(0b11111000 == MSB2LSB(0b00011111));
assert(0b11111100 == MSB2LSB(0b00111111));
assert(0b11111110 == MSB2LSB(0b01111111));
assert(0b11111111 == MSB2LSB(0b11111111));
assert(0b10101010 == MSB2LSB(0b01010101));

5

Есть много способов перевернуть биты в зависимости от того, что вы подразумеваете под «простейшим способом».


Обратное вращение

Наверное, наиболее логично, состоит в том, чтобы повернуть байт при применении маски к первому биту (n & 1):

unsigned char reverse_bits(unsigned char b)
{
    unsigned char   r = 0;
    unsigned        byte_len = 8;

    while (byte_len--) {
        r = (r << 1) | (b & 1);
        b >>= 1;
    }
    return r;
}

1) Поскольку длина неподписанного символа составляет 1 байт, что равно 8 битам, это означает, что мы будем сканировать каждый бит while (byte_len--)

2) Сначала мы проверяем, находится ли b в крайнем правом положении с помощью (b & 1); если это так, мы устанавливаем бит 1 на r с помощью |и перемещаем его всего на 1 бит влево, умножая r на 2 с помощью(r << 1)

3) Затем мы делим наш символ без знака b на 2 с помощью, b >>=1чтобы стереть бит, расположенный в крайнем правом углу переменной b. Напоминаем, что b >> = 1; эквивалентно b / = 2;


Обратный в одну строку

Это решение приписывается Ричу Шрёппелю в разделе Programming Hacks

unsigned char reverse_bits3(unsigned char b)
{
    return (b * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff;
}

1) Операция умножения (b * 0x0202020202ULL) создает пять отдельных копий 8-битного байтового шаблона для разветвления в 64-битное значение.

2) Операция И (& 0x010884422010ULL) выбирает биты, которые находятся в правильных (перевернутых) позициях относительно каждой 10-битной группы битов.

3) Вместе операции умножения и И копируют биты из исходного байта, поэтому каждая из них появляется только в одном из 10-битных наборов. Обратные позиции битов исходного байта совпадают с их относительными позициями в любом 10-битном наборе.

4) Последний шаг (% 0x3ff), который включает деление модуля на 2 ^ 10-1, имеет эффект слияния каждого набора из 10 бит (из позиций 0-9, 10-19, 20-29, ...) в 64-битном значении. Они не перекрываются, поэтому шаги сложения, лежащие в основе деления модуля, ведут себя как операции ИЛИ.


Разделяй и властвуй Решение

unsigned char reverse(unsigned char b) {
   b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
   b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
   b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
   return b;
}

Это самый популярный ответ, и, несмотря на некоторые объяснения, я думаю, что для большинства людей трудно представить себе, что на самом деле означают 0xF0, 0xCC, 0xAA, 0x0F, 0x33 и 0x55.

Он не использует преимущества '0b', который является расширением GCC и включен в стандарт C ++ 14, выпущенный в декабре 2014 года, поэтому через некоторое время после этого ответа, датированного апрелем 2010 года

Целочисленные константы могут быть записаны как двоичные константы, состоящие из последовательности цифр «0» и «1» с префиксом «0b» или «0B». Это особенно полезно в средах, которые много работают на битовом уровне (например, микроконтроллеры).

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

unsigned char reverse(unsigned char b) {
   b = (b & 0b11110000) >> 4 | (b & 0b00001111) << 4;
   b = (b & 0b11001100) >> 2 | (b & 0b00110011) << 2;
   b = (b & 0b10101010) >> 1 | (b & 0b01010101) << 1;
   return b;
}

NB: >> 4это потому, что в 1 байте 8 битов, это беззнаковый символ, поэтому мы хотим взять вторую половину и так далее.

Мы могли бы легко применить это решение к 4 байтам всего с двумя дополнительными строками и следуя той же логике. Поскольку обе маски дополняют друг друга, мы даже можем использовать ~ для переключения битов и экономии чернил:

uint32_t reverse_integer_bits(uint32_t b) {
   uint32_t mask = 0b11111111111111110000000000000000;
   b = (b & mask) >> 16 | (b & ~mask) << 16;
   mask = 0b11111111000000001111111100000000;
   b = (b & mask) >> 8 | (b & ~mask) << 8;
   mask = 0b11110000111100001111000011110000;
   b = (b & mask) >> 4 | (b & ~mask) << 4;
   mask = 0b11001100110011001100110011001100;
   b = (b & mask) >> 2 | (b & ~mask) << 2;
   mask = 0b10101010101010101010101010101010;
   b = (b & mask) >> 1 | (b & ~mask) << 1;
   return b;
}

[Только C ++] Отменить любой беззнаковый (шаблон)

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

template <class T>
T reverse_bits(T n) {
    short bits = sizeof(n) * 8; 
    T mask = ~T(0); // equivalent to uint32_t mask = 0b11111111111111111111111111111111;

    while (bits >>= 1) {
        mask ^= mask << (bits); // will convert mask to 0b00000000000000001111111111111111;
        n = (n & ~mask) >> bits | (n & mask) << bits; // divide and conquer
    }

    return n;
}

Попробуйте сами, включив указанную выше функцию:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

template <class T>
void print_binary(T n)
{   T mask = 1ULL << ((sizeof(n) * 8) - 1);  // will set the most significant bit
    for(; mask != 0; mask >>= 1) putchar('0' | !!(n & mask));
    putchar('\n');
}

int main() {
    uint32_t n = 12;
    print_binary(n);
    n = reverse_bits(n); 
    print_binary(n);
    unsigned char c = 'a';
    print_binary(c);
    c = reverse_bits(c);
    print_binary(c);
    uint16_t s = 12;
    print_binary(s);
    s = reverse_bits(s);
    print_binary(s);
    uint64_t l = 12;
    print_binary(l);
    l = reverse_bits(l);
    print_binary(l);
    return 0;
}

Реверс с asm volatile

И последнее, но не менее важное: если простота означает меньше строк, почему бы не попробовать встроенную сборку?

Вы можете протестировать приведенный ниже фрагмент кода, добавив -masm=intelпри компиляции:

unsigned char reverse_bits(unsigned char c) {
    __asm__ __volatile__ (R"(
        mov cx, 8       
    daloop:                   
        ror di          
        adc ax, ax      
        dec cx          
        jnz short daloop  
    ;)");
}

Пояснения построчно:

        mov cx, 8       ; we will reverse the 8 bits contained in one byte
    daloop:             ; while loop
        shr di          ; Shift Register `di` (containing value of the first argument of callee function) to the Right
        rcl ax          ; Rotate Carry Left: rotate ax left and add the carry from shr di, the carry is equal to 1 if one bit was "lost" from previous operation 
        dec cl          ; Decrement cx
        jnz short daloop; Jump if cx register is Not equal to Zero, else end loop and return value contained in ax register

3

Поиск по таблице или

uint8_t rev_byte(uint8_t x) {
    uint8_t y;
    uint8_t m = 1;
    while (m) {
       y >>= 1;
       if (m&x) {
          y |= 0x80;
       }
       m <<=1;
    }
    return y;
}

редактировать

Поищите здесь другие решения, которые могут вам подойти.


3

более медленная, но более простая реализация:

static int swap_bit(unsigned char unit)
{
    /*
     * swap bit[7] and bit[0]
     */
    unit = (((((unit & 0x80) >> 7) ^ (unit & 0x01)) << 7) | (unit & 0x7f));
    unit = (((((unit & 0x80) >> 7) ^ (unit & 0x01))) | (unit & 0xfe));
    unit = (((((unit & 0x80) >> 7) ^ (unit & 0x01)) << 7) | (unit & 0x7f));

    /*
     * swap bit[6] and bit[1]
     */
    unit = (((((unit & 0x40) >> 5) ^ (unit & 0x02)) << 5) | (unit & 0xbf));
    unit = (((((unit & 0x40) >> 5) ^ (unit & 0x02))) | (unit & 0xfd));
    unit = (((((unit & 0x40) >> 5) ^ (unit & 0x02)) << 5) | (unit & 0xbf));

    /*
     * swap bit[5] and bit[2]
     */
    unit = (((((unit & 0x20) >> 3) ^ (unit & 0x04)) << 3) | (unit & 0xdf));
    unit = (((((unit & 0x20) >> 3) ^ (unit & 0x04))) | (unit & 0xfb));
    unit = (((((unit & 0x20) >> 3) ^ (unit & 0x04)) << 3) | (unit & 0xdf));

    /*
     * swap bit[4] and bit[3]
     */
    unit = (((((unit & 0x10) >> 1) ^ (unit & 0x08)) << 1) | (unit & 0xef));
    unit = (((((unit & 0x10) >> 1) ^ (unit & 0x08))) | (unit & 0xf7));
    unit = (((((unit & 0x10) >> 1) ^ (unit & 0x08)) << 1) | (unit & 0xef));

    return unit;
}

3

Может ли это быть быстрым решением?

int byte_to_be_reversed = 
    ((byte_to_be_reversed>>7)&0x01)|((byte_to_be_reversed>>5)&0x02)|      
    ((byte_to_be_reversed>>3)&0x04)|((byte_to_be_reversed>>1)&0x08)| 
    ((byte_to_be_reversed<<7)&0x80)|((byte_to_be_reversed<<5)&0x40)|
    ((byte_to_be_reversed<<3)&0x20)|((byte_to_be_reversed<<1)&0x10);

Избавляет от суеты использования цикла for! а специалисты подскажите, пожалуйста, эффективнее ли это и быстрее?


У него время выполнения O (n), а не O (log₂ n), где n - количество бит (8, 16, 32, 64 и т. Д.). См. В другом месте ответы, которые выполняются за время O (log₂ n).
Тодд Леман,

2

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

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


2

Эта простая функция использует маску для проверки каждого бита входного байта и передачи его в сдвигающийся выход:

char Reverse_Bits(char input)
{    
    char output = 0;

    for (unsigned char mask = 1; mask > 0; mask <<= 1)
    {
        output <<= 1;

        if (input & mask)
            output |= 1;
    }

    return output;
}

Маска должна быть без подписи, извините.
luci88filter

1

Это одна основана на одном BobStein-VisiBone при условии

#define reverse_1byte(b)    ( ((uint8_t)b & 0b00000001) ? 0b10000000 : 0 ) | \
                            ( ((uint8_t)b & 0b00000010) ? 0b01000000 : 0 ) | \
                            ( ((uint8_t)b & 0b00000100) ? 0b00100000 : 0 ) | \
                            ( ((uint8_t)b & 0b00001000) ? 0b00010000 : 0 ) | \
                            ( ((uint8_t)b & 0b00010000) ? 0b00001000 : 0 ) | \
                            ( ((uint8_t)b & 0b00100000) ? 0b00000100 : 0 ) | \
                            ( ((uint8_t)b & 0b01000000) ? 0b00000010 : 0 ) | \
                            ( ((uint8_t)b & 0b10000000) ? 0b00000001 : 0 ) 

Мне это очень нравится, потому что компилятор автоматически выполняет всю работу за вас, поэтому дополнительные ресурсы не требуются.

это также может быть расширено до 16 бит ...

#define reverse_2byte(b)    ( ((uint16_t)b & 0b0000000000000001) ? 0b1000000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000000010) ? 0b0100000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000000100) ? 0b0010000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000001000) ? 0b0001000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000010000) ? 0b0000100000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000100000) ? 0b0000010000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000001000000) ? 0b0000001000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000010000000) ? 0b0000000100000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000100000000) ? 0b0000000010000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000001000000000) ? 0b0000000001000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000010000000000) ? 0b0000000000100000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000100000000000) ? 0b0000000000010000 : 0 ) | \
                            ( ((uint16_t)b & 0b0001000000000000) ? 0b0000000000001000 : 0 ) | \
                            ( ((uint16_t)b & 0b0010000000000000) ? 0b0000000000000100 : 0 ) | \
                            ( ((uint16_t)b & 0b0100000000000000) ? 0b0000000000000010 : 0 ) | \
                            ( ((uint16_t)b & 0b1000000000000000) ? 0b0000000000000001 : 0 ) 

Я бы заключил bв круглые скобки, если это более сложное выражение, чем одно число, и, возможно, также переименовал бы макрос REVERSE_BYTEв качестве подсказки, что вы, вероятно, не хотите иметь там более сложное (время выполнения) выражение. Или сделайте это встроенной функцией. (Но в целом мне нравится, что это достаточно просто, чтобы вы могли легко сделать это по памяти с очень небольшой вероятностью ошибки.)
Arkku


1

Если вы используете небольшой микроконтроллер и вам нужно высокоскоростное решение с небольшой занимаемой площадью, это может быть решением. Его можно использовать для проекта C, но вам нужно добавить этот файл как файл ассемблера * .asm в ваш проект C. Инструкции: В проекте C добавьте это объявление:

extern uint8_t byte_mirror(uint8_t);

Вызовите эту функцию из C

byteOutput= byte_mirror(byteInput);

Это код, он подходит только для ядра 8051. В регистре процессора r0 находятся данные из byteInput . Код повернуть вправо r0 перекрестный перенос, а затем повернуть перенос влево до r1 . Повторите эту процедуру 8 раз для каждого бита. Затем регистр r1 возвращается функции c как byteOutput. В 8051 сердечник можно вращать только аккумулятором a .

NAME     BYTE_MIRROR
RSEG     RCODE
PUBLIC   byte_mirror              //8051 core        

byte_mirror
    mov r3,#8;
loop:   
    mov a,r0;
    rrc a;
    mov r0,a;    
    mov a,r1;
    rlc a;   
    mov r1,a;
    djnz r3,loop
    mov r0,a
    ret

ПЛЮСЫ: Небольшой размер, высокая скорость. МИНУСЫ: Это не многоразовый код, он предназначен только для 8051.

011101101-> носить

101101110 <-переносить


Хотя этот код может ответить на вопрос, было бы лучше включить некоторый контекст, объясняющий, как он работает и когда его использовать. Ответы, содержащие только код, в конечном итоге бесполезны.
fNek

0
  xor ax,ax
  xor bx,bx
  mov cx,8
  mov al,original_byte!
cycle:   shr al,1
  jnc not_inc
  inc bl
not_inc: test cx,cx
  jz,end_cycle
  shl bl,1
  loop cycle
end_cycle:

обратный байт будет в регистре bl


3
В другом контексте это может быть справедливым ответом, но вопрос был о C или C ++, а не о asm ...
jadsq

0
typedef struct
{
    uint8_t b0:1;
    uint8_t b1:1;
    uint8_t b2:1;
    uint8_t b3:1;
    uint8_t b4:1;
    uint8_t b5:1;
    uint8_t b6:1;
    uint8_t b7:1;
} bits_t;

uint8_t reverse_bits(uint8_t src)
{
    uint8_t dst = 0x0;
    bits_t *src_bits = (bits_t *)&src;
    bits_t *dst_bits = (bits_t *)&dst;

    dst_bits->b0 = src_bits->b7;
    dst_bits->b1 = src_bits->b6;
    dst_bits->b2 = src_bits->b5;
    dst_bits->b3 = src_bits->b4;
    dst_bits->b4 = src_bits->b3;
    dst_bits->b5 = src_bits->b2;
    dst_bits->b6 = src_bits->b1;
    dst_bits->b7 = src_bits->b0;

    return dst;
}

В качестве стилистической заметки я нахожу использование uint8_t1-битных полей немного некрасивым, так как сначала кажется, что это займет 8 бит, но затем в конце строки определяет его как только один бит. Я бы использовал unsigned b0:1и т.д.
Arkku 08

0
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i;
    unsigned char rev = 0x70 ; // 0b01110000
    unsigned char tmp = 0;

    for(i=0;i<8;i++)
    {
    tmp |= ( ((rev & (1<<i))?1:0) << (7-i));
    }
    rev = tmp;

    printf("%x", rev);       //0b00001110 binary value of given number
    return 0;
}

Пожалуйста, добавьте пояснение.
zcui93 05

0

Я думаю это достаточно просто

uint8_t reverse(uint8_t a)
{
  unsigned w = ((a << 7) & 0x0880) | ((a << 5) & 0x0440) | ((a << 3) & 0x0220) | ((a << 1) & 0x0110);
  return static_cast<uint8_t>(w | (w>>8));
}

или

uint8_t reverse(uint8_t a)
{
  unsigned w = ((a & 0x11) << 7) | ((a & 0x22) << 5) | ((a & 0x44) << 3) | ((a & 0x88) << 1);
  return static_cast<uint8_t>(w | (w>>8));
}

0
unsigned char c ; // the original
unsigned char u = // the reversed
c>>7&0b00000001 |
c<<7&0b10000000 |
c>>5&0b00000010 |
c<<5&0b01000000 |
c>>3&0b00000100 |
c<<3&0b00100000 |
c>>1&0b00001000 |
c<<1&0b00010000 ;

Explanation: exchanged bits as per the arrows below.
01234567
<------>
#<---->#
##<-->##
###<>###

0

Я добавлю свое решение, так как пока я не могу найти ничего подобного в ответах. Возможно, он немного переоценен, но он генерирует таблицу поиска с использованием C ++ 14 std::index_sequenceво время компиляции.

#include <array>
#include <utility>

constexpr unsigned long reverse(uint8_t value) {
    uint8_t result = 0;
    for (std::size_t i = 0, j = 7; i < 8; ++i, --j) {
        result |= ((value & (1 << j)) >> j) << i;
    }
    return result;
}

template<size_t... I>
constexpr auto make_lookup_table(std::index_sequence<I...>)
{
    return std::array<uint8_t, sizeof...(I)>{reverse(I)...};   
}

template<typename Indices = std::make_index_sequence<256>>
constexpr auto bit_reverse_lookup_table()
{
    return make_lookup_table(Indices{});
}

constexpr auto lookup = bit_reverse_lookup_table();

int main(int argc)
{
    return lookup[argc];
}

https://godbolt.org/z/cSuWhF


0

Вот простое и удобочитаемое решение, переносимое на все соответствующие платформы, в том числе с sizeof(char) == sizeof(int):

#include <limits.h>

unsigned char reverse(unsigned char c) {
    int shift;
    unsigned char result = 0;

    for (shift = 0; shift < CHAR_BIT; shift++) {
        result <<= 1;
        result |= c & 1;
        c >>= 1;
    }
    return result;
}

0

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

#include <algorithm>
#include <bitset>
#include <exception>
#include <iostream>
#include <limits>
#include <string>

// helper lambda function template
template<typename T>
auto getBits = [](T value) {
    return std::bitset<sizeof(T) * CHAR_BIT>{value};
};

// Function template to flip the bits
// This will work on integral types such as int, unsigned int,
// std::uint8_t, 16_t etc. I did not test this with floating
// point types. I chose to use the `bitset` here to convert
// from T to string as I find it easier to use than some of the
// string to type or type to string conversion functions,
// especially when the bitset has a function to return a string. 
template<typename T>
T reverseBits(T& value) {
    static constexpr std::uint16_t bit_count = sizeof(T) * CHAR_BIT;

    // Do not use the helper function in this function!
    auto bits = std::bitset<bit_count>{value};
    auto str = bits.to_string();
    std::reverse(str.begin(), str.end());
    bits = std::bitset<bit_count>(str);
    return static_cast<T>( bits.to_ullong() );
}

// main program
int main() {
    try {
        std::uint8_t value = 0xE0; // 1110 0000;
        std::cout << +value << '\n'; // don't forget to promote unsigned char
        // Here is where I use the helper function to display the bit pattern
        auto bits = getBits<std::uint8_t>(value);
        std::cout << bits.to_string() << '\n';

        value = reverseBits(value);
        std::cout << +value << '\n'; // + for integer promotion

        // using helper function again...
        bits = getBits<std::uint8_t>(value);
        std::cout << bits.to_string() << '\n';

    } catch(const std::exception& e) {  
        std::cerr << e.what();
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

И это дает следующий результат.

224
11100000
7
00000111

0

Этот помог мне с набором массивов с точечной матрицей 8x8.

uint8_t mirror_bits(uint8_t var)
{
    uint8_t temp = 0;
    if ((var & 0x01))temp |= 0x80;
    if ((var & 0x02))temp |= 0x40;
    if ((var & 0x04))temp |= 0x20;
    if ((var & 0x08))temp |= 0x10;

    if ((var & 0x10))temp |= 0x08;
    if ((var & 0x20))temp |= 0x04;
    if ((var & 0x40))temp |= 0x02;
    if ((var & 0x80))temp |= 0x01;

    return temp;
}

1
Эта функция на самом деле не работает, 0b11001111 должно быть 0b11110011, но с этой функцией не работает. Тот же метод тестирования работает для многих других функций, перечисленных здесь.
Дэн

Да, спасибо, я поправил свой ответ. Спасибо, что
сообщили
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.