同事在研究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 };
由此可知,“逆天”常量并不止一个,欢迎大家参与研究、讨论。