字節反轉及字節中bit1計數

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

x&=x-1;.net

本帖轉自http://blog.csdn.net/todototry/archive/2007/04/23/1575900.aspxblog

可是更加詳細的說明以下:get

這兩個函數極非常巧妙,做了並行計算。數學

先看問題1: 反轉一個字節。
它的算法是這樣的: 首先是2位2位爲一組,交換前一半和後一半。再4位4位爲一組,交換前一半和後一半。再8位爲一組,交換前一半和後一半it


可能還有點說不清楚。我舉個例子。
將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的個數。
和問題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位都同樣。


反過來推導,基本思想仍是2分法。

給你一個x,設f(x,n)表示x中2進制位1的個數,則

f(x)=f(x & 0x0000ffffUL) + f((x >> 16) & 0x0000ffffUL)

一個32位的化成兩個16位的,16位的再化成兩個8位的,並且能夠就在x上進行計算,保持數量不變,一直1位的x自己32位。

再從前面日後走

unsigned long func(unsigned long x)
{
第一步,把x當作32個數,每一個數就是一個bit,它自己在數量上就表示它們1的個數,
5的二進制是0101,就是01010101,運算完成後,它將成爲16個數,相鄰兩個位的數聯合表示原來1的個數,好比:

   00 01 10 11     >>1=     00 00 11 01
& 01 01 01 01           & 01 01 01 01
= 00 01 00 01        =     00 00 01 01
                  +
= 00 01 01 10
=( 0   1   1   2)

完成以後,原數據將成爲相鄰2位組成一個數,共16個數(當作)。
     x = (x & 0x55555555UL) + ((x >> 1) & 0x55555555UL);


第二步,把這16個數,又每相鄰兩個相加,這時候一個數是2位,因而相與的數就是 00110011,即0x33333333UL,原理同上,運算結束後,結果又縮小爲8個數,每一個數用4位表示,表示是對應4位的1的個數,好比:

( 0 1   1 2)
   0001 0110    >>2=    0000 0101
& 0011 0011         & 0011 0011
= 0001 0010           0000 0001
                +
= 0001 0011
=(    1     3)

後面同理。      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; }

相關文章
相關標籤/搜索