神祕常量0x077CB531,德布萊英序列的恩賜

遊戲程式設計師劉宇發表於2019-05-30

轉載請註明來源https://www.cnblogs.com/xiaohutu/p/10950011.html

某天我在優化遊戲的演算法,在將一個個關鍵資料結構優化全部成位操作後,最終來到最後一座大山前,如何快速計算出這個數值的二進位制表示中最後一位的1在哪一位?

首先,我們已知:

將二進位制只保留最後一位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。

例如乘以 100 0000,(6個0,左移6位)
 00000111011111001011010100110001
-> 11011111001011010100110001000000
再右移27位
-> 11011
得到的數字是27,在陣列裡是6

很神奇,不是嗎?

仔細分析一下這個數字,可以發現,這個數字從每一位分別開始看,連續5位(到結尾迴圈),是所有5位的二進位制數字的全集,而且左移28-31位時,結尾填0,正好序列開始的幾個數字也是0。

那麼不難理解,從這個數列的第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

相關文章