• 寻找“逆天”常量


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

  • 相关阅读:
    数组中的逆序对 --剑指offer
    第一个只出现一次的字符 --剑指offer
    丑数 --剑指offer
    把数组排成最小的数 --剑指offer
    整数中1出现的次数 --剑指offer
    最小的k个数 --剑指offer
    数组中出现次数超过一半的数字 --剑指offer
    redis击穿,穿透,雪崩,分布式锁,api(jedis,luttuce)
    Java创建数据库新建表及初始化表
    generatorConfig.xml自动生成实体类,dao和xml
  • 原文地址:https://www.cnblogs.com/danielWise/p/4378460.html
Copyright © 2020-2023  润新知