C / C ++에서 바이트의 비트 순서를 바꾸는 가장 간단한 방법은 무엇입니까?


110

한 바이트에서 비트 순서를 바꾸는 방법은 여러 가지가 있지만 개발자가 구현하는 "가장 간단한"것이 무엇인지 궁금합니다. 그리고 반전하면 다음을 의미합니다.

1110 -> 0111
0010 -> 0100

이것은 PHP 질문 과 비슷하지만 중복되지는 않습니다 .

이것은 C 질문 과 유사하지만 중복되지는 않습니다 . 이 질문은 개발자가 구현하는 가장 쉬운 방법을 요구합니다. "Best Algorithm"은 메모리 및 CPU 성능과 관련됩니다.


인라인 어셈블리를 사용하십시오. 더 나은 방법은 함수를 별도의 번역 단위에 넣는 것입니다. 각 대상 플랫폼에 대해 하나의 어셈블리 언어 모듈이 있습니다. 빌드 프로세스가 모듈을 선택하게하십시오.
Thomas Matthews

@Andreas Simplest implementation
nathan

답변:


102

단일 바이트에 대해 이야기하고 있다면 어떤 이유로 256 바이트를 사용할 수없는 경우가 아니면 테이블 조회가 가장 좋은 방법 일 것입니다.


12
기성 솔루션을 복사하지 않고 간단히 구현할 수있는 것에 대해 이야기하고 있다면 조회 테이블을 만드는 데 여전히 다른 솔루션이 필요합니다. (물론 손으로 할 수도 있지만 오류가 발생하기 쉽고 시간이 많이 걸립니다…)
Arkku 2010

7
회문을 무시하면 배열을 256 바이트 미만으로 압축 할 수 있습니다.
wilhelmtell 2010

8
@wilhelmtell-어떤 것이 회문인지 알기 위해 테이블이 필요합니다.
Mark Ransom

6
@wilhelmtell : 스크립트를 작성하려면 여전히 다른 솔루션이 필요합니다. 내 요점은 룩업 테이블이 사용하기 쉽지만 작성하기가 간단하지 않습니다. (기성 룩업 테이블을 복사하는 것을 제외하고는 모든 솔루션을 복사하는 것이 좋습니다.) 예를 들어, "가장 간단한"솔루션이 시험이나 인터뷰에서 종이에 기록 될 수있는 솔루션으로 간주된다면, 저는 그렇게하지 않을 것입니다. 손으로 룩업 테이블을 만들고이를 수행하는 프로그램을 만들면 이미 다른 솔루션이 포함되어있을 것입니다 (이는 테이블과 테이블을 모두 포함하는 것보다 더 간단합니다).
Arkku 2010

4
@Arkku 내가 의미하는 것은 처음 256 바이트의 테이블과 역 매핑을 출력하는 스크립트를 작성하는 것입니다. 예, 역방향 함수를 작성하는 것으로 돌아 왔지만 이제는 가장 좋아하는 스크립팅 언어로되어 있으며 원하는대로 지저분 할 수 있습니다. 완료되는 즉시 버리고 한 번 실행하면됩니다. 스크립트의 출력을 C 코드로 가져 오십시오 unsigned int rtable[] = {0x800, 0x4000, ...};.. 그런 다음 대본을 버리고 그것을 가지고있는 것을 잊어 버리십시오. 동등한 C ++ 코드보다 작성하는 것이 훨씬 빠르며 한 번만 실행되므로 C ++ 코드에서 O (1) 런타임을 얻을 수 있습니다.
wilhelmtell 2010

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

먼저 왼쪽 4 비트가 오른쪽 4 비트와 교체됩니다. 그런 다음 모든 인접 쌍이 스왑되고 모든 인접 단일 비트가 스왑됩니다. 이로 인해 순서가 바뀝니다.


26
합리적으로 짧고 빠르지 만 간단하지는 않습니다.
Mark Ransom

3
이 접근법은 또한 엔디안을위한 바이트 스와핑을 수행하기 위해 명확하게 일반화됩니다.
Boojum

2
가장 간단한 접근 방식은 아니지만 +1을 좋아합니다.
nathan

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 2010

3
좋지만 캐시 미스를 줄 것입니다.
Johan Kotlinski 2010

7
@kotlinski : 캐시 미스의 원인은 무엇입니까? 작은 테이블 버전이 큰 버전보다 캐시 효율적일 수 있다고 생각합니다. 내 Core2에서 캐시 라인의 너비는 64 바이트이고 전체 테이블은 여러 줄에 걸쳐있는 반면 작은 테이블은 한 줄에 쉽게 맞습니다.
deft_code

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

46

많은 솔루션에 대한 비트 트위들 링 해킹 을 참조하십시오 . 거기에서 복사 붙여 넣기는 분명히 구현하기 쉽습니다. =)

예 (32 비트 CPU에서) :

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

"구현이 간단하다"는 것이 시험이나 면접에서 참조없이 수행 할 수있는 것을 의미한다면, 가장 안전한 방법은 비트를 역순으로 하나씩 다른 변수로 복사하는 비효율적 일 것입니다 (이미 다른 답변에 표시됨). ).


1
URL에서 : 32 비트 CPU : b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Joshua

1
@Joshua : 저도 개인적으로 좋아하는 것입니다. 주의 사항 (링크 된 페이지에 명시된대로)은 uint8_t에 할당되거나 캐스팅되어야하며 그렇지 않으면 상위 비트에 가비지가있을 것입니다.
Arkku 2010

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
유용합니다, 감사합니다. 내 느린 이동 방법이 임베디드 앱의 성능을 제한하는 것 같습니다. PIC의 ROM에 테이블을 배치했습니다 (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;
}

편집하다:

선택적 bitcount를 사용하여 템플릿으로 변환했습니다.


@nvl-고정. 나는 그것을 템플릿으로 만들기 시작했지만 그렇게하지 않기로 중간에 결정했다 ... 너무 많음 & gt & lt
그리고

추가 pedenatry를 들어, 교체 sizeof(T)*8와 함께 sizeof(T)*CHAR_BITS.
Pillsy 2010

6
@andand 별도의 추가 pendantry를 들어, 대체 sizeof(T)*CHAR_BIT에 의해 std::numeric_limits<T>::digits(학자 인 거의 사년 이상).
Morwenn 2014

1
CHAR_BIT, 아니 어야합니다 CHAR_BITS.
Xunie 2016

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

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 2010

3
아주 간단합니다. 어셈블리 니모닉은 프로세서의 종류에 따라 다르며 많은 종류가 있기 때문에 의사 코드에 넣었습니다. 원하는 경우 간단한 어셈블리 언어를 표시하도록 편집 할 수 있습니다.
Thomas Matthews

컴파일러 최적화가 적절한 어셈블리 명령어로 단순화되는지 확인할 수 있습니다.
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

아름다운! 지금까지 내가 가장 좋아하는.
Nick Rameau 16.11.22

이것은 확실히 간단하지만 실행 시간은 O (log₂ n)가 아닌 O (n)이며, 여기서 n은 비트 수 (8, 16, 32, 64 등)입니다.
Todd Lehman

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,
ljrk

8 비트가 CHAR_BIT있다고 가정 char할 때 왜 사용 합니까?
chqrlie

6

매우 제한된 상수 8 비트 입력의 경우이 방법은 런타임에 메모리 나 CPU가 필요하지 않습니다.

#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에 이것을 사용했습니다. 레이블은 종종 상수이며 일반적으로 8 진수입니다.

사양이이 레이블을 big-endian 205 8 진수로 정의하기 때문에 상수를 정의하는 데 사용했습니다.

#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>(또는 a boost::dynamic_bitset) 를 사용하는 이점은 바이트 나 단어에 제한되지 않고 임의의 비트 수를 반전 할 수 있다는 것입니다.

HTH


여기에서 bitset이 포드보다 더 간단한 방법은 무엇입니까? 코드를 표시하거나 표시하지 않습니다.
wilhelmtell 2010

사실, 나는 코드가 비트 셋을 뒤집은 다음 원본으로 되돌릴 것이라고 생각합니다. 변경 ii! = size (); ~ ii <size () / 2; 그리고 그것은 더 나은 일을 할 것입니다 =)
Viktor Sehr

(@ 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) unsigner char의 길이가 1 바이트 (8 비트)이므로 각 비트를 스캔 함을 의미합니다. while (byte_len--)

2) 우리는 먼저 b가 맨 오른쪽에 있는지 확인합니다 (b & 1). 그렇다면 r에 비트 1을 설정 |하고 r에 2를 곱하여 왼쪽으로 1 비트 만 이동합니다.(r << 1)

3) 그런 다음 unsigned char b를 2로 나누어 b >>=1변수 b의 맨 오른쪽에있는 비트를 지 웁니다. 다시 말해, b >> = 1; b / = 2와 동일합니다.


한 줄로 반전

이 솔루션은 프로그래밍 해킹 섹션의 Rich Schroeppel에 기인합니다.

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

1) 곱하기 연산 (b * 0x0202020202ULL)은 8 비트 바이트 패턴의 5 개의 개별 복사본을 만들어 64 비트 값으로 팬 아웃합니다.

2) AND 연산 (& 0x010884422010ULL)은 각 10 비트 비트 그룹에 대해 올바른 (반전 된) 위치에있는 비트를 선택합니다.

3) 곱하기와 AND 연산은 원래 바이트에서 비트를 복사하여 각각 10 비트 세트 중 하나에 만 나타납니다. 원래 바이트에서 반전 된 비트 위치는 10 비트 세트 내의 상대 위치와 일치합니다.

4) 모듈러스를 2 ^ 10-1로 나누는 마지막 단계 (% 0x3ff)는 각 10 비트 세트를 병합하는 효과가 있습니다 (위치 0-9, 10-19, 20-29, ...). 64 비트 값입니다. 겹치지 않으므로 계수 분할의 기본이되는 추가 단계는 OR 연산처럼 작동합니다.


분할 및 정복 솔루션

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가 의미하는 바를 시각화하는 것이 어렵다고 생각합니다.

GCC 확장 이며 2014 년 12 월에 릴리스 된 C ++ 14 표준 이후에 포함 된 '0b'를 활용하지 않으므로이 답변은 2010 년 4 월부터 시작되었습니다.

정수 상수는 '0b'또는 '0B'접두사가 붙은 '0'및 '1'숫자 시퀀스로 구성된 이진 상수로 쓸 수 있습니다. 이는 마이크로 컨트롤러와 같이 비트 수준에서 많이 작동하는 환경에서 특히 유용합니다.

이 솔루션을 반으로 반으로 이동하는이 솔루션을 더 잘 기억하고 이해하려면 아래 코드 스 니펫을 확인하십시오.

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 : >> 41 바이트에 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 ++ 전용] 부호없는 모든 항목 (템플릿) 반전

위의 논리는 모든 유형의 unsigned에서 작동하는 루프로 요약 할 수 있습니다.

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 휘발성으로 반전

마지막으로, 가장 단순한 것이 더 적은 라인을 의미한다면 어셈블리를 인라인 해 보는 것은 어떨까요?

-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 (log₂ n)가 아닌 O (n)이며, 여기서 n은 비트 수 (8, 16, 32, 64 등)입니다. O (log₂ n) 시간에 실행되는 답변은 다른 곳을 참조하십시오.
Todd Lehman

2

알고리즘 솔루션을 구현하기 전에 사용중인 CPU 아키텍처에 대한 어셈블리 언어를 확인하십시오. 아키텍처에는 이와 같은 비트 조작을 처리하는 명령이 포함될 수 있습니다 (그리고 단일 어셈블리 명령보다 더 간단한 것은 무엇입니까?).

이러한 지침을 사용할 수없는 경우 조회 테이블 경로를 사용하는 것이 좋습니다. 스크립트 / 프로그램을 작성하여 테이블을 생성 할 수 있으며 조회 작업은 여기에있는 비트 반전 알고리즘보다 빠릅니다 (조회 테이블을 어딘가에 저장해야하는 비용).


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 Arkku

1

컴파일러가 unsigned long long을 허용한다고 가정합니다 .

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

여기에서 발견


1

소형 마이크로 컨트롤러를 사용하고 있고 설치 공간이 작은 고속 솔루션이 필요한 경우 이것이 솔루션이 될 수 있습니다. C 프로젝트에서 사용할 수 있지만이 파일을 C 프로젝트에 어셈블러 파일 * .asm으로 추가해야합니다. 지침 : C 프로젝트에서 다음 선언을 추가하십시오.

extern uint8_t byte_mirror(uint8_t);

C에서이 함수 호출

byteOutput= byte_mirror(byteInput);

이것은 코드이며 8051 코어에만 적합합니다. CPU 레지스터에서 r0byteInput의 데이터입니다 . 코드 오른쪽 회전 r0 크로스 캐리 다음 캐리 왼쪽을 r1 로 회전 합니다. 모든 비트에 대해이 절차를 8 회 반복합니다. 그런 다음 레지스터 r1이 byteOutput으로 c 함수에 반환됩니다. 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
공정한 대답 일 수 있지만 질문은 asm이 아닌 C 또는 C ++에 관한 것입니다.
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

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

지금까지 답변에서 이와 같은 것을 찾을 수 없기 때문에 솔루션에 칩을 넣을 것입니다. 아마도 약간 과도하게 설계되었지만 std::index_sequence컴파일 타임에 C ++ 14 를 사용하여 조회 테이블을 생성합니다 .

#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이어야하지만이 함수에서는 실패합니다. 여기에 나열된 다른 많은 기능에 대해서도 동일한 테스트 방법이 작동합니다.
Dan

네, 내 대답을 수정 해 주셔서 감사합니다. 내 실수에 대해 알려 주셔서 감사합니다. :)
R1S8K
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.