У C / C ++ який найпростіший спосіб змінити порядок бітів у байті?


110

Хоча існує кілька способів повернути бітовий порядок у байті, мені цікаво, що саме "найпростіший" для розробника. І під реверсом я маю на увазі:

1110 -> 0111
0010 -> 0100

Це схоже на, але не дублікат цього питання щодо PHP.

Це схоже на, але не дублікат цього C питання. Це запитання задає найпростіший метод, який можна реалізувати розробником. «Найкращий алгоритм» стосується пам’яті та продуктивності процесора.


Використовуйте вбудовану збірку. Краще, покладіть функцію в окремий блок перекладу. Майте по одному мовному модулю мовлення для кожної цільової платформи. Нехай процес збирання вибирає модулі.
Томас Меттьюз

Реалізація @Andreas Найпростіші
Nathan

Відповіді:


102

Якщо ви говорите про один байт, пошук таблиць, мабуть, найкращий варіант, якщо тільки з якоїсь причини у вас немає 256 байт.


12
Якщо ми говоримо про те, що просто реалізувати без копіювання готового рішення, для створення таблиці пошуку все ж потрібне інше рішення. (Звичайно, можна зробити це вручну, але це схильне до помилок і забирає багато часу ...)
Аркку

7
Ви можете стиснути масив на дещо менше 256 байт, якщо проігнорувати паліндроми.
wilhelmtell

8
@wilhelmtell - вам знадобиться таблиця, щоб знати, які з них є паліндромами.
Марк Рансом

6
@wilhelmtell: Ну, для написання сценарію все-таки потрібне інше рішення, яке було моє значення - таблиця пошуку проста у використанні, але не проста у створенні. (За винятком того, як скопіювати готову таблицю пошуку, але тоді можна просто скопіювати будь-яке рішення.) Наприклад, якщо "найпростішим" рішенням вважається таке, яке можна було б написати на папері в іспиті чи на співбесіді, я б не почати робити таблицю пошуку вручну і змусити програму це робити, вона вже включатиме інше рішення (яке було б простіше, ніж те, що включає в себе і таблицю).
Арку

4
@Arkku, що я мав на увазі, це написати сценарій, який виводить таблицю з перших 256 байт і їх зворотне відображення. Так, ви повернулися до написання зворотної функції, але тепер на улюбленій мові сценаріїв, і це може бути настільки неприємно, як вам захочеться - ви збираєтесь викинути її, як тільки це буде зроблено, і ви запустили її один раз. Є висновок скрипта як код C, навіть: unsigned int rtable[] = {0x800, 0x4000, ...};. Потім киньте сценарій і забудьте, що у вас його колись було. Написати набагато швидше, ніж еквівалентний код C ++, і він буде працювати колись один раз, тому ви отримаєте O (1) час виконання у своєму C ++ коді.
wilhelmtell

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.
Натан

7
Так, це просто. Це свого роду алгоритм розділення та підкорення. Відмінно!
kiewic

Це швидше, ніж метод, запропонований @Arkku нижче?
qed

122

Я думаю, що таблиця пошуку повинна бути одним з найпростіших методів. Однак вам не потрібна повна таблиця пошуку.

//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

3
Приємно, але дасть вам пропустити кеш.
Йохан Котлінський

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

4
@kotlinski: Temporal місцевість є більш важливим для звернень в кеш або замінних стратегій, ніж адреса локальності
CFI

6
@Harshdeep: Розглянемо двійкові кодовані індекси записів таблиці. індекс b0000 (0) -> b0000 (0x0) нудний; b0001(1) -> b1000(0x8), b0010(2) -> b0100(0x4), b1010(10) -> b0101(0x5). Бачите візерунок? Це досить просто, що ви можете обчислити його в голові (якщо ви можете читати двійкове, інакше вам знадобиться папір, щоб це опрацювати). Що стосується стрибка, що реверсування 8-бітового цілого числа є таким же, як повернення 4-бітових частин, а потім їх заміна; Я претендую на досвід та інтуїцію (або магію).
deft_code

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, або у верхній біт буде сміття.
Арку

41

Оскільки ніхто не опублікував повне рішення пошуку таблиці, ось моє:

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).
відлетів


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
іі

Для додаткової педератури замініть sizeof(T)*8на sizeof(T)*CHAR_BITS.
Пілсі

6
@andand Щоб отримати додаткову підвіску, замініть sizeof(T)*CHAR_BITїї std::numeric_limits<T>::digits(майже 4 роки пізніше).
Морвен

1
Це має бути CHAR_BIT, ні CHAR_BITS.
Xunie

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);

"оригінал" - байт, який ви хочете змінити. "reversed" - результат, ініціалізований до 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

3
Це досить просто. Я вклав це в псевдокод, тому що мнемоніка монтажу є специфічною для породи процесорів, і там є багато порід. Якщо ви хочете, я можу відредагувати це, щоб показати просту мову монтажу.
Томас Меттьюз

Можна було побачити, чи оптимізація компілятора спрощується до відповідної інструкції по збірці.
Спаркі

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

Для дуже обмеженого випадку постійного 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

Можливо, вас зацікавлять 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>(або aboost::dynamic_bitset ) полягає в тому, що ви не обмежені байтами чи словами, але можете змінити довільну кількість біт.

HTH


Як тут бит простіше, ніж стручок? Покажіть код, чи його немає.
wilhelmtell

По суті, я думаю, що цей код змінить біт, а потім поверне його до початкового. Змінити ii! = Розмір (); до ii <розмір () / 2; і це зробить кращу роботу =)
Віктор

(@ viktor-sehr ні, не буде, rev відрізняється від bs). У будь-якому випадку мені сама відповідь не подобається: я думаю, що це випадок, коли двійкові арифметичні та оператори зсуву краще підходять. Це все ще залишається найпростішим для розуміння.
baol

Як щодо std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); - безпосередньо використання зворотних ітераторів?
MSalters

@MSalters Мені подобається незмінність цього.
baol

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) Тоді ділимо наш неподписаний char b на 2, b >>=1щоб стерти біт, розташований в крайньому правому куті змінної b. Як нагадування, b >> = 1; еквівалентно b / = 2;


Реверс в одну лінію

Це рішення приписується Річ Шроппель у розділі Програмування хаків

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

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

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

3) Разом множення та операції AND копіюють біти з початкового байта, щоб вони з'являлися лише в одному з 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;
}

Реверс з золотою летючою

І останнє, але не менш важливе значення, якщо найпростіші означають меншу кількість рядків, чому б не спробувати вбудовану збірку?

Ви можете перевірити фрагмент коду нижче, додавши -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);

Позбавляє від суєти використання циклу! але експерти, будь ласка, скажіть мені, чи це ефективно та швидше?


Це час виконання - це O (n), а не O (log₂ n), де n - кількість біт (8, 16, 32, 64 тощо). Дивіться в іншому місці відповіді, які виконуються в O (логін) час.
Тодд Леман

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як натяк на те, що ви, мабуть, не хочете там мати більш складний (час виконання) вираз. Або зробити його вбудованою функцією. (Але в цілому мені подобається, що це досить просто, щоб ви могли це легко зробити з пам’яті, маючи дуже невеликі шанси помилок.)
Аркку

1

Припустимо, що ваш компілятор дозволяє довго не підписувати :

unsigned char reverse(unsigned char b) {
  return (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;
}

Тут виявлено


1

Якщо ви використовуєте невеликий мікроконтролер і потребуєте швидкісного рішення з невеликим слідом, це можуть бути рішення. Можна використовувати його для проекту C, але цей файл потрібно додати як файл асемблера * .asm до вашого проекту C. Інструкції: У проект C додайте цю декларацію:

extern uint8_t byte_mirror(uint8_t);

Викликайте цю функцію з C

byteOutput= byte_mirror(byteInput);

Це код, він підходить лише для 8051 core. У регістрі CPU 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

ПРО: Це невеликий слід, це висока швидкість CONS: Це код не для багаторазового використання, це лише для 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
В іншому контексті це може бути справедливою відповіддю, але питання
стосувалося

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_tдля 1-бітових полів трохи некрасивим, оскільки, здається, спочатку кажуть, що це займе 8 біт, але потім у кінці рядка визначає його як лише один біт. Я б використав unsigned b0:1і т. Д.
Аркку

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

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, але не працює з цією функцією. Цей же метод тестування працює для багатьох інших функцій, перерахованих тут.
День

Так, дякую, що я виправив свою відповідь. Дякую за те, що повідомили про свою помилку :)
R1S8K
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.