尋找「逆天」常量

  同事在研究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。ui

(v & -v) * 0x077CB531 的意思是將常量0x077CB531 向左移位6位(左移6位至關於乘64)。spa

((uint)(v & -v) * 0x077CB5310) >> 27 位的意思是繼續將上一步的結果向右移位27位,由於01串總長度是32位,向右移27位之後低位只剩下5個bits。設計

而0x077CB5310 的二進制表示形式爲00000111011111001011010100110001, 因此上面的步驟至關於以下代碼:code

static int GetMultiplyDeBruijnBitPosition(uint v)
{
    return MultiplyDeBruijnBitPosition[27];
}

根據上面的常量數組,可知當v 等於123456時,其(v & -v) 的二進制表示行爲末尾含有6個0。orm

這個算法的用處目前看主要有兩種:blog

1. 快速計算log2(v & -v);ip

2. 任意給定兩個32-bit 的整型數組,對其中的數據進行異或運算,獲得的值v, 採用如上算法判斷第幾位是不一樣的,從而用於壓縮算法。rem

 

  以上是關於這個常量的簡要介紹,下面重點介紹下這個常量的特色:

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 

相關文章
相關標籤/搜索