用0x077CB531計算末尾0的個數

2021-06-21 13:46:59 字數 2154 閱讀 9491

下面這個位運算小技巧可以迅速給出乙個數的二進位制表達中末尾有多少個 0 。比如, 123 456 的二進位制表達是 1 11100010 01000000 ,因此這個程式給出的結果就是 6 。

unsigned int v;  // find the number of trailing zeros in 32-bit v

int r;           // result goes here

static const int multiplydebruijnbitposition[32] =

;r = multiplydebruijnbitposition[((uint32_t)((v & -v) * 0x077cb531u)) >> 27];

熟悉位運算的朋友們可以認出, v & -v 的作用就是取出右起連續的 0 以及首次出現的 1 。當 v = 123 456 時, v & -v 就等於 64 ,即二進位制的 1000000 。怪就怪在,這個 0x077cb531 是怎麼回事?陣列 multiplydebruijnbitposition 又是什麼玩意兒呢?

這還得從 0x077cb531 本身的乙個性質開始說起。把這個常數寫成 32 位二進位制,可以得到

00000111011111001011010100110001

這個 01 串有乙個無比牛 b 的地方:如果把它看作是迴圈的,它正好包含了全部 32 種可能的 5 位 01 串,既無重複,又無遺漏!其實,這樣的 01 串並不稀奇,因為構造這樣的 01 串完全等價於尋找乙個有向圖中的 euler 迴路。如下圖,構造乙個包含 16 個頂點的圖,頂點分別命名為 0000, 0001, 0010, …, 1111 。如果某個點的後 3 位,正好等於另乙個點的前 3 位,就畫一條從前者出發指向後者的箭頭。也就是說,只要兩個頂點上的數滿足 abcd 和 bcde 的關係( a 、 b 、 c 、 d 、 e 可能代表相同的數字),就從 abcd 出發,連一條到 bcde 的路,這條路就記作 abcde 。注意,有些點之間是可以相互到達的(比如 1010 和 0101 ),有些點甚至有一條到達自己的路(比如 0000 )。

構造乙個字串使其包含所有可能的 5 位 01 子串,其實就相當於沿著箭頭在上圖中游走的過程。不妨假設字串以 0000 開頭。如果下乙個數字是 1 ,那麼 00001 這個子串就被包含了,同時最新的 4 位數就變成了 0001 ;但若下乙個數字還是 0 ,那麼 00000 就被包含了進來,最新的 4 個數仍然是 0000 。從圖上看,這無非是乙個從 0000 點出發走了哪條路的問題:你是選擇了沿 00001 這條路走到了 0001 這個點,還是沿著 00000 這條路走回了 0000 這個點。同理,每新增乙個數字,就相當於沿著某條路走到了乙個新的點,路上所寫的 5 位數就是剛被考慮到的 5 位數。我們的目的便是既無重複又無遺漏地遍歷所有的路。顯然圖中的每個頂點入度和出度都是 2 ,因此這個圖一定存在 euler 迴路,我們便能輕易構造出乙個滿足要求的 01 串了。這樣的 01 串就叫做 de bruijn 序列。

de bruijn 序列在這裡究竟有什麼用呢?它的用途其實很簡單,就是為 32 種不同的情況提供了乙個唯一索引。比方說, 1000000 後面有 6 個 0 ,將 1000000 乘以 0x077cb531 ,就得到

00000111011111001011010100110001

-> 11011111001011010100110001000000

相當於把 de bruijn 序列左移了 6 位。再把這個數右移 27 位,就相當於提取出了這個數的頭 5 位:

11011111001011010100110001000000

->                            11011

由於 de bruijn 序列的性質,因此當輸入數字的末尾 0 個數不同時,最後得到的這個 5 位數也不同。而陣列 multiplydebruijnbitposition 則相當於乙個字典的功能。 11011 轉回十進位制是 27 ,於是我們查一查 multiplydebruijnbitposition[27] ,程式即返回 6 。

注意到當輸入數字的末尾 0 個數超過 27 個時,程式也是正確的,因為左移時低位正好是用 0 填充的。

這段神一般的**取自 

徹底搞定0x0d和0x0a

我只在arm linux c和vc 下做了試驗,請大家在接觸其它語言環境下,小心推廣,不行就自己動手做試驗,最可靠。在arm linux c和vc 下回車換行的意義如下。回車 cr ascii碼 r 十六進製制,0x0d,回車的作用只是移動游標至該行的起始位置 換行 lf ascii碼 n 十六進製...

關於0x0d與0x0a的ASCII。

今天發現乙個有趣的現象 在 ma 我用的版本是6.11 中作彙編時發現,0x0d與0x0a有著不同的作用。比如 dead for dream 在這個字串後只加上0x0d則得到 游標移到開頭的那個d下面,而沒有換行 再輸入字元的話,將原來的字元著改掉。在這個字串上只加上0x0a則得到 游標移到末尾m字...

語言雜記 0x0A

今天寫乙個字串反轉練練手,涉及到手動輸入字串時出現了一些問題 char s scanf s s 採用字元陣列的方式輸入的時候完全沒有問題,但是採用下面的方式的時候就會報錯 核心以轉儲 char s scanf s s 什麼是核心已轉儲呢?通俗的理解就是訪問了不能訪問的內容,如下 所以我們的char ...