C / C ++ 'da bir bayttaki bit sırasını tersine çevirmenin en basit yolu nedir?


110

Bir bayttaki bit sırasını tersine çevirmenin birden fazla yolu olsa da, bir geliştiricinin uygulayabileceği "en basit" olanı merak ediyorum. Ve tersine çevirmekle şunu demek istiyorum:

1110 -> 0111
0010 -> 0100

Bu, benzer, ancak bu PHP sorusunun bir kopyası değildir .

Bu, bu C sorusuna benzer, ancak bir kopyası değildir . Bu soru, bir geliştirici tarafından uygulanacak en kolay yöntemi soruyor. "En İyi Algoritma" bellek ve cpu performansıyla ilgilidir.


Satır içi montajı kullanın. Daha iyisi, işlevi ayrı bir çeviri birimine yerleştirin. Her hedef platform için bir montaj dili modülüne sahip olun. Bırakın derleme süreci modülleri seçsin.
Thomas Matthews

@Andreas En basit uygulama
nathan

Yanıtlar:


102

Tek bir bayttan bahsediyorsanız, herhangi bir nedenle 256 baytınız yoksa, tablo arama muhtemelen en iyi seçenektir.


12
Hazır bir çözümü kopyalamadan uygulanması basit bir şeyden bahsediyorsak, arama tablosunu oluşturmak yine de başka bir çözüm gerektirir. (Elbette elle de yapılabilir, ancak bu hataya meyillidir ve zaman alıcıdır…)
Arkku

7
Palindromları görmezden gelirseniz, diziyi 256 bayttan biraz daha azına sıkıştırabilirsiniz.
wilhelmtell

8
@wilhelmtell - hangilerinin palindrom olduğunu bilmek için bir tabloya ihtiyacınız var.
Mark Ransom

6
@wilhelmtell: Senaryoyu yazmak için birinin hala başka bir çözüme ihtiyacı var ki bu benim açımdan - bir arama tablosunun kullanımı basit, ancak oluşturulması basit değil. (Hazır bir arama tablosunun kopyalanması dışında, ancak o zaman herhangi bir çözümü de kopyalayabilirim.) Örneğin, "en basit" çözüm, bir sınavda veya görüşmede kağıda yazılabilecek bir çözüm olarak kabul edilirse, yapmam elle bir arama tablosu yapmaya başlayın ve programın bunu yapmasını sağlamak zaten farklı bir çözüm içerecektir (bu, hem onu ​​hem de tabloyu içeren olandan tek başına daha basit olacaktır).
Arkku

4
@Arkku demek istediğim, ilk 256 baytlık tabloyu ve ters eşlemelerini çıkaran bir betik yazmaktır. Evet, ters işlevi yazmaya geri döndünüz, ama şimdi en sevdiğiniz betik diliyle ve istediğiniz kadar kötü olabilir - biter bitmez onu atacaksınız ve bir kez çalıştırdınız. Hatta C kodu olarak komut dosyasının çıkışı var: unsigned int rtable[] = {0x800, 0x4000, ...};. Sonra senaryoyu bir kenara at ve ona sahip olduğunu unut. Eşdeğer C ++ kodundan yazmak çok daha hızlıdır ve yalnızca bir kez çalışır, bu nedenle C ++ kodunuzda O (1) çalışma zamanı elde edersiniz.
wilhelmtell

227

Bu çalışmalı:

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

Önce soldaki dört bit, sağdaki dört bit ile değiştirilir. Daha sonra tüm bitişik çiftler değiştirilir ve ardından tüm bitişik tek bitler değiştirilir. Bu, siparişin tersine çevrilmesine neden olur.


26
Oldukça kısa ve hızlı, ancak basit değil.
Mark Ransom

3
Bu yaklaşım ayrıca, bitmek için bayt değiş tokuşu gerçekleştirmek için temiz bir şekilde genelleştirir.
Boojum

2
En basit yaklaşım değil, ama +1 seviyorum.
nathan

7
Evet, çok basit. Bu bir çeşit böl ve yönet algoritması. Mükemmel!
kiewic

Aşağıdaki @Arkku tarafından önerilen yöntemden daha hızlı mı?
13'te

123

Bence arama tablosu en basit yöntemlerden biri olmalı. Bununla birlikte, tam bir arama tablosuna ihtiyacınız yoktur.

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

Bu, görsel olarak kodlamak ve doğrulamak için oldukça basittir.
Nihayetinde bu, tam bir tablodan daha hızlı bile olabilir. Bit ariti ucuzdur ve tablo bir önbellek satırına kolayca sığar.


10
Bu, tablo çözümünün karmaşıklığını azaltmanın mükemmel bir yoludur. +1
James

3
Güzel, ama size bir önbellek özledim.
Johan Kotlinski

7
@kotlinski: Bir önbellek kaçırmaya ne sebep olur? Küçük tablo sürümünün büyük olandan daha fazla önbellek verimli olabileceğini düşünüyorum. Core2'mde bir önbellek satırı 64 bayt genişliğinde, tam tablo birden çok satıra yayılırken, daha küçük tablo bir satıra kolayca sığar.
deft_code

4
@kotlinski: Geçici yerellik, önbellek isabetleri veya değiştirme stratejileri için adres yerellikten daha önemlidir
cfi

6
@Harshdeep: Tablo girişlerinin ikili kodlanmış dizinlerini düşünün. indeks b0000 (0) -> b0000 (0x0) sıkıcı; b0001(1) -> b1000(0x8), b0010(2) -> b0100(0x4), b1010(10) -> b0101(0x5). Modeli görüyor musun? Kafanızdan hesaplayabileceğiniz kadar basittir (ikiliyi okuyabiliyorsanız, aksi halde hesaplamak için kağıda ihtiyacınız olacak). 8 bitlik bir tamsayıyı tersine çevirmenin, 4 bitlik parçaların tersine çevrilmesi ve ardından değiştirilmesiyle aynı anlama geldiği; Deneyim ve sezgi (veya sihir) iddia ediyorum.
deft_code

46

Ufak tefek hileleri görünPek çok çözüm için . Oradan kopyalamanın uygulanması açıkça basittir. =)

Örneğin (32 bit CPU'da):

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

"Uygulaması basit" derken, bir sınavda veya iş görüşmesinde referans olmadan yapılabilecek bir şey kastediliyorsa, o zaman en güvenli bahis muhtemelen bitlerin teker teker başka bir değişkene ters sırada verimsiz kopyalanmasıdır (halihazırda diğer cevaplarda gösterilmiştir) ).


1
URL'nizden: 32 bit CPU: b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Joshua

1
@ Joshua: Bu benim de kişisel favorim. Uyarı (bağlantılı sayfada belirtildiği gibi) bir uint8_t'ye atanması veya atanması gerektiğidir, aksi takdirde üst bitlerde çöp olacaktır.
Arkku

42

Hiç kimse eksiksiz bir tablo arama çözümü yayınlamadığına göre, işte benimki:

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
Yararlı, teşekkürler. Görünüşe göre, daha yavaş kaydırma yöntemim, yerleşik bir uygulamadaki performansı sınırlıyor. Bir PIC'de ROM'a yerleştirilmiş tablo (rom anahtar kelimesinin eklenmesiyle).
uçun


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

DÜZENLE:

İsteğe bağlı bit sayısı ile bir şablona dönüştürüldü


@nvl - düzeltildi. Bunu bir şablon olarak oluşturmaya başladım, ancak yarı yolda bunu yapmamaya karar verdim ... çok fazla & gt & lt
ve

Ekstra pedenatry için, yerini sizeof(T)*8ile sizeof(T)*CHAR_BITS.
Pillsy

6
@andand ekstra ekstra pendantry için, yerine sizeof(T)*CHAR_BITgöre std::numeric_limits<T>::digits(ukalalıklarının Neredeyse 4 yıldır sonrası).
Morwenn

1
Olmalı CHAR_BIT, değil CHAR_BITS.
Xunie

1
rv = (rv << 1) | (n & 0x01);
Vignesh

16

İki çizgi:

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

veya "0b1" bölümü ile ilgili sorunlarınız olması durumunda:

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

"orijinal", tersine çevirmek istediğiniz bayttır. "tersine çevrilmiş", 0 olarak başlatılan sonuçtur.


14

Muhtemelen taşınabilir olmasa da, assembly dilini kullanırdım.
Çoğu montaj dilinin, biraz taşıma bayrağına döndürme ve taşıma bayrağını kelimeye (veya bayta) döndürme talimatları vardır.

Algoritma:

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

Bunun için yüksek seviyeli dil kodu çok daha karmaşıktır, çünkü C ve C ++ taşımak için döndürmeyi ve taşımadan döndürmeyi desteklemez. Taşıma bayrağı modellenmelidir.

Düzenleme: Örneğin Assembly dili

;  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
Sanırım bu cevap basitin tam tersi. Taşınabilir olmayan, derleme ve gerçek derleme yerine sözde kodla yazılacak kadar karmaşık.
deft_code

3
Oldukça basit. Sözde koda koydum çünkü montaj anımsatıcıları bir işlemci türüne özeldir ve orada birçok tür vardır. İsterseniz, bunu basit montaj dilini gösterecek şekilde düzenleyebilirim.
Thomas Matthews

Bir derleyici optimizasyonunun uygun bir montaj talimatına dönüştürülüp basitleştirilmediği görülebilir.
Sparky

12

Aşağıdaki çözümü, burada gördüğüm diğer biraz uğraşma algoritmalarından daha basit buluyorum.

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

Bayttaki her biti alır ve ilkinden sonuncuya doğru buna göre değiştirir.

Açıklama:

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

Güzel! Şimdiye kadarki favorim.
Nick Rameau

Bu kesinlikle basittir, ancak çalıştırma süresinin O (log₂ n) yerine O (n) olduğu, burada n bit sayısıdır (8, 16, 32, 64, vb.).
Todd Lehman

10

En basit yol, muhtemelen bir döngüdeki bit konumlarını yinelemektir:

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" olmadan
ljrk

8 bit CHAR_BITolduğunu varsaydığınızda neden kullanasınız char?
chqrlie

6

İlginizi çekebilir std::vector<bool>(bu biraz dolu) vestd::bitset

İstendiği gibi en basit olmalıdır.

#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;
}

Diğer seçenekler daha hızlı olabilir.

DÜZENLEME: Size bir çözüm borçluyum 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;
}

İkinci örnek, c ++ 0x uzantısı gerektirir (diziyi ile başlatmak için {...}). A bitsetveya a std::vector<bool>(veya a) kullanmanın avantajıboost::dynamic_bitset ) bayt veya sözcüklerle sınırlı olmamanız, ancak rastgele sayıda biti ters çevirebilmenizdir.

HTH


Bit kümesi burada bir kapsülden nasıl daha basit? Kodu gösterin ya da göstermeyin.
wilhelmtell

Aslında, bence bu kod bit kümesini tersine çevirecek ve sonra onu orijinaline geri döndürecektir. İi değiştir ! = Boyut (); için ii <boyutu () / 2; ve daha iyi bir iş çıkaracak =)
Viktor Sehr

(@ viktor-sehr hayır, olmayacak, rev bs'den farklıdır). Her neyse, cevabı kendim beğenmedim: Sanırım bu ikili aritmetik ve kaydırma operatörlerinin daha uygun olduğu bir durum. Hala anlaşılması en basit olanıdır.
baol

std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); Ters yineleyicileri doğrudan kullanmaya ne dersiniz ?
MSalters

@MSalters Değişmezliğini seviyorum.
baol

6

Çok sınırlı sabit, 8-bit giriş durumu için, bu yöntem çalışma zamanında bellek veya CPU gerektirmez:

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

Bunu, etiketin bit sırasının (endianness) kelimenin geri kalanının karşısında olduğu ARINC-429 için kullandım. Etiket genellikle sabittir ve geleneksel olarak sekizlik biçimdedir.

Bunu bir sabiti tanımlamak için nasıl kullandım, çünkü spesifikasyon bu etiketi big-endian 205 sekizlik olarak tanımlıyor.

#define LABEL_HF_COMM MSB2LSB(0205)

Daha fazla örnek:

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

"En basit yol" ile ne kastettiğinize bağlı olarak bitleri tersine çevirmenin birçok yolu vardır.


Döndürerek Ters Çevir

Muhtemelen en mantıklı olanı, ilk bit üzerine bir maske uygularken baytı döndürmekten ibarettir (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) Bir işaretsiz karakterin uzunluğu 1 bayt olduğu için, bu 8 bit'e eşittir, bu, her biti tarayacağımız anlamına gelir while (byte_len--)

2) Önce b'nin aşırı sağda olup olmadığını kontrol ederiz (b & 1); eğer öyleyse r için bit 1'i| r'yi 2 ile çarparak 1 bit sola hareket ettiririz(r << 1)

3) Sonra b >>=1, b değişkeninin en sağında bulunan biti silmek için işaretsiz b karakterimizi 2'ye böleriz . Hatırlatmak gerekirse, b >> = 1; b / = 2'ye eşdeğerdir;


Tek Satırda Ters Çevir

Bu çözüm, Programlama Hackleri bölümünde Rich Schroeppel'e atfedilir

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

1) Çoğaltma işlemi (b * 0x0202020202ULL), 64 bitlik bir değere yayılmak üzere 8 bitlik bayt modelinin beş ayrı kopyasını oluşturur.

2) AND işlemi (& 0x010884422010ULL), her 10 bitlik bit grubuna göre doğru (ters) konumlarda olan bitleri seçer.

3) Çarpma ve AND işlemleri birlikte orijinal bayttan bitleri kopyalar, böylece her biri 10 bitlik kümelerden yalnızca birinde görünür. Orijinal bayttan bitlerin ters çevrilmiş pozisyonları, herhangi bir 10 bitlik set içindeki göreceli pozisyonları ile çakışır.

4) Modüllerin 2 ^ 10 - 1'e bölünmesini içeren son adım (% 0x3ff), her 10 bitlik kümeyi birleştirme etkisine sahiptir (0-9, 10-19, 20-29, ... konumlarından) 64 bit değerinde. Örtüşmezler, bu nedenle modül bölünmesinin altında yatan toplama adımları OR işlemleri gibi davranır.


Böl ve Fethet Çözümü

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

Bu en çok oylanan cevap ve bazı açıklamalara rağmen, çoğu insan için 0xF0, 0xCC, 0xAA, 0x0F, 0x33 ve 0x55'in gerçekte ne anlama geldiğini görselleştirmenin zor olduğunu düşünüyorum.

Bir GCC uzantısı olan ve Aralık 2014'te piyasaya sürülen C ++ 14 standardından bu yana dahil edilen '0b'den yararlanmıyor , bu nedenle Nisan 2010'dan kalma bu cevaptan bir süre sonra

Tamsayı sabitleri, '0' ve '1' basamaklarının önüne '0b' veya '0B' eklenmiş ikili sabitler olarak yazılabilir. Bu, özellikle çok fazla bit seviyesinde çalışan ortamlarda (mikro denetleyiciler gibi) kullanışlıdır.

Yarı yarıya ilerlediğimiz bu çözümü daha da iyi hatırlamak ve anlamak için lütfen aşağıdaki kod parçalarını kontrol edin:

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

Not: Bunun >> 4nedeni, 1 baytta 8 bit olmasıdır, bu işaretsiz bir karakterdir, bu yüzden diğer yarısını almak istiyoruz, vb.

Bu çözümü sadece iki ek satırla ve aynı mantığı izleyerek 4 bayta kolayca uygulayabiliriz. Her iki maske de birbirini tamamladığından, bitleri değiştirmek ve biraz mürekkep tasarrufu yapmak için ~ kullanabiliriz:

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

[Yalnızca C ++] Tüm İşaretsizleri Ters Çevir (Şablon)

Yukarıdaki mantık, her tür işaretsiz üzerinde çalışacak bir döngü ile özetlenebilir:

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

Yukarıdaki işlevi dahil ederek kendiniz deneyin:

#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 ile ters çevirin

Son olarak, en basit olanı daha az satır anlamına geliyorsa, neden satır içi montajı denemeyesiniz?

Aşağıdaki kod parçacığını -masm=intelderlerken ekleyerek test edebilirsiniz :

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

Açıklamalar satır satır:

        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

Tablo araması veya

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

Düzenle

Sizin için daha iyi sonuç verebilecek diğer çözümler için buraya bakın


3

daha yavaş ama daha basit bir uygulama:

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

Bu hızlı çözüm olabilir mi?

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

Bir for döngüsü kullanmanın karmaşasından kurtulur! ancak uzmanlar lütfen bunun verimli ve hızlı olup olmadığını söyler mi?


Bunun yürütme zamanı O (log₂ n) yerine O (n) olup, burada n bit sayısıdır (8, 16, 32, 64, vb.). O (log₂ n) zamanında yürütülen yanıtlar için başka bir yere bakın.
Todd Lehman

2

Herhangi bir algoritmik çözümü uygulamadan önce, kullandığınız CPU mimarisi için montaj dilini kontrol edin. Mimariniz, bunun gibi bit düzeyinde manipülasyonları işleyen talimatlar içerebilir (ve tek bir montaj talimatından daha basit ne olabilir?).

Böyle bir talimat mevcut değilse, arama tablosu rotasına gitmenizi öneririm. Tabloyu sizin için oluşturmak için bir komut dosyası / program yazabilirsiniz ve arama işlemleri buradaki bit ters çevirme algoritmalarının herhangi birinden daha hızlı olacaktır (arama tablosunu bir yerde saklamak pahasına).


2

Bu basit işlev, giriş baytındaki her biti test etmek ve onu değişen bir çıktıya aktarmak için bir maske kullanır:

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

Üzgünüm, maske imzasız olmalı.
luci88filter

1

Bu, sağlanan BobStein-VisiBone'a dayanmaktadır

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

Bunu gerçekten çok seviyorum çünkü derleyici işi sizin için otomatik olarak hallediyor, dolayısıyla başka kaynak gerektirmiyor.

bu aynı zamanda 16-Bit'e kadar uzatılabilir ...

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

bTek bir sayıdan daha karmaşık bir ifade olması durumunda parantez içine koyardım ve belki de makroyu, REVERSE_BYTEmuhtemelen orada daha karmaşık (çalışma zamanı) bir ifadeye sahip olmak istemediğinize dair bir ipucu olarak yeniden adlandırırım . Veya onu bir satır içi işlev yapın. (Ama genel olarak bunu hafızadan çok az hata şansı ile kolayca yapabileceğiniz kadar basit olduğu için seviyorum.)
Arkku

1

Derleyicinizin unsigned long long'a izin verdiğini varsayarsak :

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

Burada keşfedildi


1

Küçük mikro denetleyici kullanıyorsanız ve az yer kaplayan yüksek hızlı çözüme ihtiyacınız varsa, bu çözüm olabilir. C projesi için kullanmak mümkündür, ancak bu dosyayı C projenize assembler dosyası * .asm olarak eklemeniz gerekir. Talimatlar: C projesinde bu beyanı ekleyin:

extern uint8_t byte_mirror(uint8_t);

Bu işlevi C'den çağırın

byteOutput= byte_mirror(byteInput);

Bu koddur, sadece 8051 çekirdeği için uygundur. CPU kaydında r0 , byteInput'tan gelen verilerdir . Kod sağa döndür r0 çapraz taşıma ve sonra taşımayı sola r1'e döndür . Bu prosedürü her bit için 8 kez tekrarlayın. Daha sonra r1 kaydı, byteOutput olarak c işlevine döndürülür. 8051'de çekirdek yalnızca akümülatörü döndürmek için mümkündür 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

PROS: Az yer kaplar, yüksek hızlı CONS: Yeniden kullanılabilir kod değildir, sadece 8051 içindir.

011101101-> taşıma

101101110 <-carry


Bu kod soruyu yanıtlasa da, nasıl çalıştığını ve ne zaman kullanılacağını açıklayan bir bağlam eklemek daha iyi olacaktır. Yalnızca kod yanıtları uzun vadede kullanışlı değildir.
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:

tersine çevrilmiş bayt bl yazmacında olacaktır


3
Adil bir cevap olabilecek başka bir bağlamda, soru C veya C ++ hakkındaydı, asm değil ...
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;
}

Biçimsel bir not olarak, uint8_t1 bitlik alanlar için kullanımını biraz çirkin buluyorum , çünkü önce 8 bit alacağını söylüyor gibi görünüyor ama sonra satırın sonunda onu sadece tek bir bit olarak tanımlıyor. unsigned b0:1Vb kullanırdım
Arkku

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

Lütfen biraz açıklama ekleyin.
zcui93

0

Bence bu yeterince basit

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

veya

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

Şimdiye kadar cevaplarda buna benzer bir şey bulamadığım için çözümümü ekleyeceğim. Belki biraz fazla yapılandırılmıştır, ancak std::index_sequencederleme zamanında C ++ 14 kullanarak arama tablosu oluşturur .

#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

Aşağıdakilere sahip olanlar da dahil olmak üzere tüm uyumlu platformlara taşınabilir basit ve okunabilir bir çözüm aşağıda verilmiştir 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

Bu sorunun tarihli olduğunu biliyorum ama yine de konunun bazı amaçlarla alakalı olduğunu düşünüyorum ve işte çok iyi çalışan ve okunabilir bir versiyon. En hızlı veya en verimli olduğunu söyleyemem ama en temizlerinden biri olmalı. Bit desenlerini kolayca görüntülemek için yardımcı bir işlev de ekledim. Bu işlev, kendi bit manipülatörünüzü yazmak yerine bazı standart kitaplık işlevlerini kullanır.

#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;
}

Ve aşağıdaki çıktıyı verir.

224
11100000
7
00000111

0

Bu, 8x8 nokta vuruşlu dizilerle bana yardımcı oldu.

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
Bu işlev aslında çalışmaz, 0b11001111'in tersi 0b11110011 olmalıdır, ancak bu işlevde başarısız olur. Aynı test yöntemi, burada listelenen diğer işlevlerin çoğu için de geçerlidir.
Dan

Evet, teşekkürler cevabımı düzelttim.
Hatamı
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.