1. 程式人生 > >二進位制位的翻轉和二進位制表示中1的個數

二進位制位的翻轉和二進位制表示中1的個數

但是更加詳細的說明如下:

這兩個函式極很是巧妙,作了平行計算。

問題1: 將某數的二進位制中各位翻轉

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

unsigned int Bit_Reverse(unsigned int v)
{
    v = ((v >> 1) & 0x55555555) | ((v << 1) & 0xaaaaaaaa);
    v = ((v >> 2) & 0x33333333) | ((v << 2) & 0xcccccccc);
    v = ((v >> 4) & 0x0f0f0f0f) | ((v << 4) & 0xf0f0f0f0);
    v = ((v >> 8) & 0x00ff00ff) | ((v << 8) & 0xff00ff00);
    v = ((v >> 16) & 0x0000ffff) | ((v << 16) & 0xffff0000);
    return v;
}


演算法是這樣的: 首先是2位2位為一組,交換前一半和後一半。再4位4位為一組,交換前一半和後一半。再8位為一組,交換前一半和後一半。

可能還有點說不清楚。我舉個例子。
將1 2 3 4 5 6 7 8 反轉。
(1)2個2個為一組,交換前一半和後一半, 變成。
   2 1 4 3 6 5 8 7
(2)4個4個為一組,交換前一半和後一半, 變成
   4 3 2 1 8 7 6 5
(3)再8個為一組,交換前一半和後一半, 變成
   8 7 6 5 4 3 2 1
反轉成功。
這樣的演算法本來很是簡單,很容易用數學歸納法證明其正確。這函式, 巧妙就巧妙在作了平行計算,分組,它一次就計算完了。

先看第一個語句。c = ( c & 0x55) << 1 | ( c & 0xAA ) >> 1;
0x55其實就是01010101, 0xAA就是10101010
假設 c=abcdefgh
c & 0x55 = 0b0d0f0h,     c & 0xAA = a0c0e0g0
跟著,前者左移一位, b0d0f0h0, 後者右移一位, 0a0c0e0g, 再一個|運算,就兩位兩位交換了位置。
想象一下,你有一個長紙條,分成一格一格,每格寫一個字,假如你將紙條每隔一格剪一個小洞,滑一格,覆蓋在原來的紙條上,你就會看到兩個兩個字交換了位置。
(注: |運算可以換成+運算,想一想為什麼)

第二個語句。 c = ( c & 0x33 ) << 2 | ( c & 0xCC ) >> 2;
0x33 = 00110011, 0xCC=11001100。

第三個語句。c = ( c & 0x0F ) << 4 | ( c & 0xF0 ) >> 4;
0x0f = 00001111, 0xF0=11110000.

這兩個語句的作用也是 分組,將一半位變成0,移位滑動,跟著再組合,就分組交換了位置。
不防想象兩個小紙條剪洞疊加。

這方法應該可以推廣。

理解了問題1,也就很容易理解問題2了.

問題2: 判斷32位整數二進位制中1的個數。

unsigned long func(unsigned long x)
{
    x = (x & 0x55555555UL) + ((x >> 1) & 0x55555555UL);
    x = (x & 0x33333333UL) + ((x >> 2) & 0x33333333UL);
    x = (x & 0x0f0f0f0fUL) + ((x >> 4) & 0x0f0f0f0fUL);
    x = (x & 0x00ff00ffUL) + ((x >> 8) & 0x00ff00ffUL);
    x = (x & 0x0000ffffUL) + ((x >> 16) & 0x0000ffffUL);
    return x;
}

和問題1一樣,也是採用了分組平行計算。

基本方法是: 2位2位為一組,相加,看看有幾個1。再4位4位為一組,相加,看看有幾個1......
還是說的不太明白。接著分析。

為了簡單說明,先看看8位的情形。相應地,函式裡面的語句變成。
x = (x & 0x55) + ((x >> 1) & 0x55);    (1)
x = (x & 0x33) + ((x >> 2) & 0x33);    (2)
x = (x & 0x0f) + ((x >> 4) & 0x0f);    (3)
return x;

假設x=abcdefgh. 0x55=01010101
x & 0x55 = 0b0d0f0h.   (x>>1) & 0x55 = 0a0c0e0g。相加。就可以知道2位2位一組1的個數。

比如x=11111111
x= (x & 0x55) + ((x >> 1) & 0x55); 之後x=10101010。你2位2位地看,10=2, 就是2 2 2 2, 就是說各組都是2個1。
比如x=00101001
x= (x & 0x55) + ((x >> 1) & 0x55); 之後x=00010101。你2位2位地看,就是0 1 1 1, 前1組只有0個1,後面的組都是1個1。

好啦。再來看。0x33=00110011。
x=abcdefgh.
x=(x & 0x33)+((x >> 2)&0x33); 相當於, 00ab00ef + 00cd00gh。
因為語句(1)之後。ab指示了頭兩位有多少個1,cd指示了下兩位有多少個1。相加00ab+00cd就指示前4位有多少個1。這樣就是4位4位為一組。注意這樣的分組,組與組之間永遠都不會產生進位的。正因為不會產生進位,才可以分開來看。

好啦。下面的過程都是一樣的,不再多說。
8位,16位,32位都一樣。