你們或許還記得 Quake III 裏面的一段有如天書般的代碼,其中用到的神祕常量 0x5F3759DF 到底是怎麼一回事,着實讓很多人傷透了腦筋。今天,我見到了一段一樣詭異的代碼。
下面這個位運算小技巧能夠迅速給出一個數的二進制表達中末尾有多少個 0 。好比, 123 456 的二進制表達是 1 11100010 01000000 ,所以這個程序給出的結果就是 6 。html
unsigned int v; // find the number of trailing zeros in 32-bit v
int r; // result goes here
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];
算法
熟悉位運算的朋友們能夠認出, v & -v 的做用就是取出右起連續的 0 以及首次出現的 1 。當 v = 123 456 時, v & -v 就等於 64 ,即二進制的 1000000 。怪就怪在,這個 0x077CB531 是怎麼回事?數組 MultiplyDeBruijnBitPosition 又是什麼玩意兒呢?數組
這還得從 0x077CB531 自己的一個性質開始提及。把這個常數寫成 32 位二進制,能夠獲得post
00000111011111001011010100110001
ui
這個 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 )。this
構造一個字符串使其包含全部可能的 5 位 01 子串,其實就至關於沿着箭頭在上圖中游走的過程。不妨假設字符串以 0000 開頭。若是下一個數字是 1 ,那麼 00001 這個子串就被包含了,同時最新的 4 位數就變成了 0001 ;但若下一個數字仍是 0 ,那麼 00000 就被包含了進來,最新的 4 個數仍然是 0000 。從圖上看,這無非是一個從 0000 點出發走了哪條路的問題:你是選擇了沿 00001 這條路走到了 0001 這個點,仍是沿着 00000 這條路走回了 0000 這個點。同理,每添加一個數字,就至關於沿着某條路走到了一個新的點,路上所寫的 5 位數就是剛被考慮到的 5 位數。咱們的目的即是既無重複又無遺漏地遍歷全部的路。顯然圖中的每一個頂點入度和出度都是 2 ,所以這個圖必定存在 Euler 迴路,咱們便能輕易構造出一個知足要求的 01 串了。這樣的 01 串就叫作 De Bruijn 序列。spa
De Bruijn 序列在這裏究竟有什麼用呢?它的用途其實很簡單,就是爲 32 種不一樣的狀況提供了一個惟一索引。比方說, 1000000 後面有 6 個 0 ,將 1000000 乘以 0x077CB531 ,就獲得設計
00000111011111001011010100110001
-> 11011111001011010100110001000000
code
至關於把 De Bruijn 序列左移了 6 位。再把這個數右移 27 位,就至關於提取出了這個數的頭 5 位:orm
11011111001011010100110001000000
-> 11011
因爲 De Bruijn 序列的性質,所以當輸入數字的末尾 0 個數不一樣時,最後獲得的這個 5 位數也不一樣。而數組 MultiplyDeBruijnBitPosition 則至關於一個字典的功能。 11011 轉回十進制是 27 ,因而咱們查一查 MultiplyDeBruijnBitPosition[27] ,程序即返回 6 。
注意到當輸入數字的末尾 0 個數超過 27 個時,程序也是正確的,由於左移時低位正好是用 0 填充的。
這段神通常的代碼取自 http://graphics.stanford.edu/~seander/bithacks.html ,歡迎你們前去圍觀。
同事在研究LZ4 壓縮算法時候給我發來了一段代碼,看完了頓時表示很是震驚:
static const int[] MultiplyDeBruijnBitPosition = new int[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
};
/// <summary>
/// Find the number of trailing zeros in 32-bit.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
static int GetMultiplyDeBruijnBitPosition(uint v)
{
return MultiplyDeBruijnBitPosition[((uint)((v & -v) * 0x077CB531U)) >> 27];
}
下面依次解釋下這段代碼的意思:
假設變量v=123456, 那麼其二進制表示形式爲(...)11110001001000000, -v 在計算機中的二進制表示形式爲(...)00001110111000000, 因此(v & -v) == 1000000, 十進制表示形式爲64。
(v & -v) * 0x077CB531 的意思是將常量0x077CB531 向左移位6位(左移6位至關於乘64)。
((uint)(v & -v) * 0x077CB5310) >> 27 位的意思是繼續將上一步的結果向右移位27位,由於01串總長度是32位,向右移27位之後低位只剩下5個bits。
而0x077CB5310 的二進制表示形式爲00000111011111001011010100110001, 因此上面的步驟至關於以下代碼:
static int GetMultiplyDeBruijnBitPosition(uint v)
{
return MultiplyDeBruijnBitPosition[27];
}
根據上面的常量數組,可知當v 等於123456時,其(v & -v) 的二進制表示行爲末尾含有6個0。
這個算法的用處目前看主要有兩種:
1. 快速計算log2(v & -v);
2. 任意給定兩個32-bit 的整型數組,對其中的數據進行異或運算,獲得的值v, 採用如上算法判斷第幾位是不一樣的,從而用於壓縮算法。
以上是關於這個常量的簡要介紹,下面重點介紹下這個常量的特色:
1. 32-bit 長度;
2. 上一個5 bits 長度的01串的後四位是下一個01串的前四位,好比10001 的下一位是00010/00011;
3. 首尾是循環的;
根據以上3條規則,設計查找常量值算法代碼以下:
using System;
using System.Collections.Generic;
namespace Test
{
class Program
{
static List<string> deBruijnList = new List<string>();
static List<string> deBruijnReserveList = new List<string>();
static string[] flagArray = new string[] { "0", "1" };
static readonly int DeBruijnLength = 5;
static readonly double MaxDeBruijnListCount = Math.Pow(2, DeBruijnLength) - 4;
static readonly uint ConstOne = 0x077CB531;
static readonly uint ConstTwo = 0x0653ADF1;
static void Init()
{
deBruijnReserveList.Add("00010");
deBruijnReserveList.Add("00100");
deBruijnReserveList.Add("01000");
deBruijnReserveList.Add("10000");
}
static uint[] GetConstArray(uint constInt)
{
//uint constInt = 0x077CB531;
uint[] constArray = new uint[32];
uint j = 0;
for (int i = 0; i < constArray.Length; i++)
{
j = (uint)((constInt << i)) >> 27;
constArray[j] = (uint)i;
}
return constArray;
}
static const int[] MultiplyDeBruijnBitPosition = new int[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
};
/// <summary>
/// Find the number of trailing zeros in 32-bit.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
static int GetMultiplyDeBruijnBitPosition(uint v)
{
return MultiplyDeBruijnBitPosition[((uint)((v & -v) * 0x077CB531U)) >> 27];
}
static void GetDeBruijnKeyStr()
{
string deBruijnStr = "00000111011111001011010100110001";
for (int i = 0; i < deBruijnStr.Length - DeBruijnLength; i++)
{
Console.WriteLine(deBruijnStr.Substring(i, DeBruijnLength));
}
}
static void GetDeBruijnKey(string currentKey)
{
string currentKeysLast4ValueStr = currentKey.Substring(1);
string nextKeyFormer4ValueStr = currentKeysLast4ValueStr;
string nextKeyFlagZero = nextKeyFormer4ValueStr + "0";
string nextKeyFlagOne = nextKeyFormer4ValueStr + "1";
if (deBruijnList.Count == MaxDeBruijnListCount)
{
return;
}
else if (deBruijnList.Count > MaxDeBruijnListCount)
{
deBruijnList.Remove(currentKey);
return;
}
if ((deBruijnList.Contains(nextKeyFlagZero) || deBruijnReserveList.Contains(nextKeyFlagZero))
&& (deBruijnList.Contains(nextKeyFlagOne) || deBruijnReserveList.Contains(nextKeyFlagOne)))
{
deBruijnList.Remove(currentKey);
return;
}
if (!deBruijnList.Contains(nextKeyFlagZero) && !deBruijnReserveList.Contains(nextKeyFlagZero))
{
deBruijnList.Add(nextKeyFlagZero);
GetDeBruijnKey(nextKeyFlagZero);
}
if (!deBruijnList.Contains(nextKeyFlagOne) && !deBruijnReserveList.Contains(nextKeyFlagOne))
{
deBruijnList.Add(nextKeyFlagOne);
GetDeBruijnKey(nextKeyFlagOne);
}
//No new entry was added, so just remove the parent key.
int lastIndexOfDeBruijnList = deBruijnList.Count - 1;
if (deBruijnList[lastIndexOfDeBruijnList] == currentKey)
{
deBruijnList.Remove(currentKey);
}
}
static void Main(string[] args)
{
Init();
GetDeBruijnKey("00000");
foreach (string deBruijnStr in deBruijnList)
{
Console.WriteLine(deBruijnStr);
}
Console.ReadLine();
}
}
}
最後獲得的新的「逆天」常量值爲0x0653ADF1U, 根據常量能夠獲得常量數組,算法以下:
//ConstOne = 0x077CB531;
//ConstOne = 0x0653ADF1;
static uint[] GetConstArray(uint constInt)
{
//uint constInt = 0x077CB531;
uint[] constArray = new uint[32];
uint j = 0;
for (int i = 0; i < constArray.Length; i++)
{
j = (uint)((constInt << i)) >> 27;
constArray[j] = (uint)i;
}
return constArray;
}
新的常量數組以下:
static const int[] MultiplyDeBruijnBitPosition2 = new int[32]
{
0, 1, 28, 2, 29, 7, 3, 12, 30, 10, 8, 17, 4, 19, 13, 22,
31, 27, 6, 11, 9, 16, 18, 21, 26, 5, 15, 20, 25, 14, 24, 23
};
由此可知,「逆天」常量並不止一個,歡迎你們參與研究、討論。
參考連接:http://www.matrix67.com/blog/archives/3985