variable precision SWAR算法

計算二進制形式中1的數量這種問題,在各類刷題網站上比較常見,以往都是選擇最笨的遍歷方法「矇混」過關。在瞭解Redis的過程當中接觸到了variable precision SWAR算法(如下簡稱VP-SWAR算法),算法異常簡潔,是目前已知的同類方法中最快的。但若是對於位運算不是很熟悉的話,卻不必定容易理解,因此有必要記錄一下。java

下面先看看VP-SWAR算法的完整實現,而後再逐行解釋。算法

  public int vpSWAR(int i){
    i = (i & 0x55555555) + ((i>>1) & 0x55555555);
    i = (i & 0x33333333) + ((i>>2) & 0x33333333);
    i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F);
    i = (i * 0x01010101) >> 24;
    return i;
  }

VP-SWAR算法分爲四步,第一步網站

i = (i & 0x55555555) + ((i>>1) & 0x55555555);

第一步的做用是計算每兩位爲一組的二進制形式包含1的個數。要理解這句話,咱們須要從二進制的角度看看到底發生了什麼。首先, 0x55555555 的二進制表示爲 0101 0101 0101 0101 0101 0101 0101 0101 ,這個數字的規律是基數位爲1,偶數位爲0。爲了簡單起見,咱們只考慮兩位的狀況,即 總共有四種狀況:spa

 

i b i & b 結果
00 01 00
01 01 01
10 01 00
11 01 01


 觀察發現, i & (0b01) 是i的基數位對應b的1位,i的偶數位對應着b的0位, i & (0b01) 的結果會將I的偶數位置爲0,而基數位保持不變,獲得的結果就是i的基數位包含1的個數。 (i >> 1) & 0x55555555 先將i右移一位,也就是將i的基數位對應b的0位,i的偶數位對應着b的1位,而後再與 0x55555555 按位與,計算出來的是i的偶數位包含1的個數。兩個計算結果相加就獲得i每兩位爲一組中包含的1的數量,咱們最後須要的就是這每兩位一組的和。3d

第二步是在第一步的基礎上,計算每四位爲一組包含1的個數。按照每2位爲一組分組用到了 0x55555555 這個數,那麼天然的,按照每4位爲一組分組天然就須要 0b0011 這種形式,這就是使用 0x33333333 的緣由。理論上, i & (0b0011) 總共有16種狀況,可是四位二進制位最多包含4個1,用二進制表示爲 0b0100 ,因此通過第一步以後,i最多有5種取值,以下:code

i b i & b 結果
0000 0011 0000
0001 0011 0001
0010 0011 0010
0011 0011 0011
0100 0011 0000

 

觀察發現, i & (0b0011) 獲得的是i的低兩位包含的1的個數,  (i >> 2) & 0b0011 )獲得的是i的高兩位包含的1的個數,兩個結果相加獲得每四位包含的1的個數。注意,這裏並非說任何數與 0b0011 按位與獲得的都是低兩位包含的1的個數,這裏的前提是第一步的計算,由於通過第一步計算以後,每兩位包含多少個1已經記錄了下來,再和 0b0011 按位與才獲得正確的結果。例如, 0x0010 & 0x 0011=0x0010 ,可是咱們不能說 0x0010 包含兩個1,可是若是 0x0010 是通過第一步的計算得來,那才說明 0x0010 記錄原始數據低兩位有兩個1。blog

第三步在第二步基礎上,計算每8位有多少個1,由 0x010x0011 ,很天然想到 0x00001111 ,其對應的32位的十六進制數就是 0x0F0F0F0Fci

第四步就頗有意思了,它再也不是計算每16位包含1的個數,而是直接計算32位包含1的個數。對於32位的數來講,能夠將其按每8位一組分爲4組,分別用ABCD表示,例如 0x01020304 用這種形式表示爲:io

 

 

 假設 0x01020304 是通過前三步計算以後獲得的結果,那麼要計算其總共包含多少個1,只需計算A+B+C+D。而ABCD表示的是不一樣的位區間範圍,不能直接相加,該如何快速計算A+B+C+D的值呢?這裏又用到了移位運算,將B、C、D分別左移8位、16位、24位,使其分別與A對齊:table

 

 咱們發現,將數字i分別左移0位、8位、16位、24位而後相加的結果,就是 i * 0x01010101 ,由於 i + (i << 8) + (i << 16) + (i << 24) = i * (1 + 1 << 8 + 1 << 16 + 1 << 24) = i * 0x01010101 。對於32位數字來講,左移以後超過32位的部分會被捨棄,低位補0,將左移以後獲得的四個數字相加,結果的高8位的值就是原32位數包含的1的個數,要獲得這個值,只須要將結果右移24位,將值放在低8位便可。

到這裏,整個算法就結束了,右移的結果就是1的數量。在Redis中,BITCOUNT命令同時使用了查表法和VP-SWAR這兩種方法。當要計算的位數小於128位時,使用查表法,不然使用VP-SWAR算法。其中查表法的作法是,程序先存一個256長度的表,按順序記錄從0-255(即 0b00000000 - 0b11111111) 數中二進制1的個數,而後對於輸入參數每8位查一次表。

相關文章
相關標籤/搜索