本文發佈於遊戲程序員劉宇的我的博客, 轉載請註明來源http://www.javashuo.com/article/p-dpbionnn-bq.htmlhtml
某天我在優化遊戲的算法,在將一個個關鍵數據結構優化所有成位操做後,最終來到最後一座大山前,如何快速計算出這個數值的二進制表示中最後一位的1在哪一位?程序員
首先,咱們已知:算法
v & -v 的原理 已知IEEE對有符號整數中負數的定義是全部數值位取反+1,首位填1,首位這樣正負數加起來既能夠爲0。 例如:一個8位的整數 A = 0001 1000, 取反 0110 0111, 取反加1 0110 1000,首位填1獲得 -A = 1110 1000 A + -A 正好加到最高一位進位後爲 0000 0000 由於取反的時候加1,因此A最後一個爲1的位取反後爲0,下面咱們稱爲第N位 取反後的第N位爲0,後面全爲1,再加1後的數值上第N位變成1,後面全爲0 此時A和-A裏,第N位以後的位全爲0,第N位以前的位全爲反 因此兩個數進行與操做,只有第N位爲1 即: 0001 1000 & 1110 1000 = 0000 1000
那麼,如何將v&-v轉換成N呢?編程
我看到了一段代碼:數組
unsigned int v; int r; static const int MultiplyDeBruijnBitPosition[32] = { 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];
計算過程能夠理解爲:數據結構
將0x077CB531U的二進制:優化
00000111011111001011010100110001
乘以 v&-v,即左移N位,再右移27位,獲得的常數在MultiplyDeBruijnBitPosition裏查表,獲得的結果便是N。ui
例如乘以 100 0000,(6個0,左移6位) 00000111011111001011010100110001 -> 11011111001011010100110001000000 再右移27位 -> 11011 獲得的數字是27,在數組裏是6
很神奇,不是嗎?spa
仔細分析一下這個數字,能夠發現,這個數字從每一位分別開始看,連續5位(到結尾循環),是全部5位的二進制數字的全集,並且左移28-31位時,結尾填0,正好序列開始的幾個數字也是0。.net
那麼不難理解,從這個數列的第X位任意取5位,均可以獲得一個0-31的數字,而且根據查表取出這個數字對應是左移過幾位。
把二進制依次寫出,若是是兩位,咱們讓每一個兩位數字的最後一位等於下一個兩位數字的第一位, 00-01-11-10,寫出 0011,長度爲4。
三位,咱們讓每一個三個數字的後兩位等於下一個數字前兩位,001-011-111-110-101-010-000,寫出00111010,長度爲8。
四位,見圖:
依此類推,到第N位,咱們可讓每一個數的後N-1位等於下一個數字的前N-1位,獲得長度爲 2的N次方長度的2進制序列。
這就是德布萊英原理:必定存在長度爲2的N次方長度的二進制串,循環來看,一位位移動,能夠完整描述全部N位長度的二進制數字的集合。
連接1:https://en.wikipedia.org/wiki/De_Bruijn_sequence
連接2:https://baike.baidu.com/item/德布萊英序列/18898516?fr=aladdin
稍微通過研究能夠發現,Debrujin序列是密碼學中運用很普遍的序列,已知原理,能夠編程來實現自動求序列的代碼。
1. 暴力遍歷
2. 遞歸法 https://blog.csdn.net/lusongno1/article/details/51104737
3. 本原多項式方法 https://blog.csdn.net/sea_sky_cloud/article/details/80932402