大家或许还记得 Quake III 里面的一段有如天书般的代码,其中用到的神秘常量 0x5F3759DF 究竟是怎么一回事,着实让不少人伤透了脑筋。今天,我见到了一段同样诡异的代码。
    下面这个位运算小技巧可以迅速给出一个数的二进制表达中末尾有多少个 0 。比如, 123 456 的二进制表达是 1 11100010 01000000 ,因此这个程序给出的结果就是 6 。

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 位二进制,可以得到

00000111011111001011010100110001

这个 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 )。

构造一个字符串使其包含所有可能的 5 位 01 子串,其实就相当于沿着箭头在上图中游走的过程。不妨假设字符串以 0000 开头。如果下一个数字是 1 ,那么 00001 这个子串就被包含了,同时最新的 4 位数就变成了 0001 ;但若下一个数字还是 0 ,那么 00000 就被包含了进来,最新的 4 个数仍然是 0000 。从图上看,这无非是一个从 0000 点出发走了哪条路的问题:你是选择了沿 00001 这条路走到了 0001 这个点,还是沿着 00000 这条路走回了 0000 这个点。同理,每添加一个数字,就相当于沿着某条路走到了一个新的点,路上所写的 5 位数就是刚被考虑到的 5 位数。我们的目的便是既无重复又无遗漏地遍历所有的路。显然图中的每个顶点入度和出度都是 2 ,因此这个图一定存在 Euler 回路,我们便能轻易构造出一个满足要求的 01 串了。这样的 01 串就叫做 De Bruijn 序列。

De Bruijn 序列在这里究竟有什么用呢?它的用途其实很简单,就是为 32 种不同的情况提供了一个唯一索引。比方说, 1000000 后面有 6 个 0 ,将 1000000 乘以 0x077CB531 ,就得到

   00000111011111001011010100110001
-> 11011111001011010100110001000000

相当于把 De Bruijn 序列左移了 6 位。再把这个数右移 27 位,就相当于提取出了这个数的头 5 位:

   11011111001011010100110001000000
->                            11011

由于 De Bruijn 序列的性质,因此当输入数字的末尾 0 个数不同时,最后得到的这个 5 位数也不同。而数组 MultiplyDeBruijnBitPosition 则相当于一个字典的功能。 11011 转回十进制是 27 ,于是我们查一查 MultiplyDeBruijnBitPosition[27] ,程序即返回 6 。
    注意到当输入数字的末尾 0 个数超过 27 个时,程序也是正确的,因为左移时低位正好是用 0 填充的。

这段神一般的代码取自 http://graphics.stanford.edu/~seander/bithacks.html ,欢迎大家前去围观。

出处:http://www.matrix67.com/blog/archives/3985

=============================================================================

同事在研究LZ4 压缩算法时候给我发来了一段代码,看完了顿时表示非常震惊:

static const int[] MultiplyDeBruijnBitPosition = new int[]
{
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , ,
}; /// <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)) >> ];
}

下面依次解释下这段代码的意思:

假设变量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[];
}

根据上面的常量数组,可知当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[] { "", "" };
static readonly int DeBruijnLength = ;
static readonly double MaxDeBruijnListCount = Math.Pow(, DeBruijnLength) - ;
static readonly uint ConstOne = 0x077CB531;
static readonly uint ConstTwo = 0x0653ADF1; static void Init()
{
deBruijnReserveList.Add("");
deBruijnReserveList.Add("");
deBruijnReserveList.Add("");
deBruijnReserveList.Add("");
} static uint[] GetConstArray(uint constInt)
{
//uint constInt = 0x077CB531;
uint[] constArray = new uint[];
uint j = ;
for (int i = ; i < constArray.Length; i++)
{
j = (uint)((constInt << i)) >> ;
constArray[j] = (uint)i;
} return constArray;
} static const int[] MultiplyDeBruijnBitPosition = new int[]
{
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , ,
}; /// <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)) >> ];
} static void GetDeBruijnKeyStr()
{
string deBruijnStr = "";
for (int i = ; i < deBruijnStr.Length - DeBruijnLength; i++)
{
Console.WriteLine(deBruijnStr.Substring(i, DeBruijnLength));
}
}
static void GetDeBruijnKey(string currentKey)
{
string currentKeysLast4ValueStr = currentKey.Substring();
string nextKeyFormer4ValueStr = currentKeysLast4ValueStr; string nextKeyFlagZero = nextKeyFormer4ValueStr + "";
string nextKeyFlagOne = nextKeyFormer4ValueStr + ""; 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 - ;
if (deBruijnList[lastIndexOfDeBruijnList] == currentKey)
{
deBruijnList.Remove(currentKey);
}
} static void Main(string[] args)
{
Init();
GetDeBruijnKey("");
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[];
uint j = ;
for (int i = ; i < constArray.Length; i++)
{
j = (uint)((constInt << i)) >> ;
constArray[j] = (uint)i;
} return constArray;
}

新的常量数组如下:

static const int[] MultiplyDeBruijnBitPosition2 = new int[]
{
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , ,
};

由此可知,“逆天”常量并不止一个,欢迎大家参与研究、讨论。

参考链接:http://www.matrix67.com/blog/archives/3985

出处:http://www.cnblogs.com/danielWise/p/4378460.html

神秘常量!用0x077CB531计算末尾0的个数,32位数首位相连的更多相关文章

  1. 神秘常量复出!用0x077CB531计算末尾0的个数 -- De Bruijn 序列

    http://www.matrix67.com/blog/archives/3985 神秘常量复出!用0x077CB531计算末尾0的个数 大家或许还记得 Quake III 里面的一段有如天书般的代 ...

  2. 用0x077CB531计算末尾0的个数

    http://www.matrix67.com/blog/archives/3985 unsigned int v;  // find the number of trailing zeros in ...

  3. Java 计算N阶乘末尾0的个数-LeetCode 172 Factorial Trailing Zeroes

    题目 Given an integer n, return the number of trailing zeroes in n!. Note: Your solution should be in ...

  4. 【CodeChef】Factorial(n!末尾0的个数)

    The most important part of a GSM network is so called Base Transceiver Station (BTS). These transcei ...

  5. 牛客小白月赛6 水题 求n!在m进制下末尾0的个数 数论

    链接:https://www.nowcoder.com/acm/contest/135/C来源:牛客网 题目描述 其中,f(1)=1;f(2)=1;Z皇后的方案数:即在Z×Z的棋盘上放置Z个皇后,使其 ...

  6. N的阶乘末尾0的个数和其二进制表示中最后位1的位置

    问题一解法:     我们知道求N的阶乘结果末尾0的个数也就是说我们在从1做到N的乘法的时候里面产生了多少个10, 我们可以这样分解,也就是将从0到N的数分解成因式,再将这些因式相乘,那么里面有多少个 ...

  7. LightOj 1090 - Trailing Zeroes (II)---求末尾0的个数

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1090 题意:给你四个数 n, r, p, q 求C(n, r) * p^q的结果中末尾 ...

  8. Algorithm --> 求阶乘末尾0的个数

    求阶乘末尾0的个数 (1)给定一个整数N,那么N的阶乘N!末尾有多少个0?比如:N=10,N!=3628800,N!的末尾有2个0. (2)求N!的二进制表示中最低位为1的位置. 第一题 考虑哪些数相 ...

  9. 求N的阶乘N!中末尾0的个数

    求N的阶乘N!中末尾0的个数 有道问题是这样的:给定一个正整数N,那么N的阶乘N!末尾中有多少个0呢?例如:N=10,N=3628800,则N!的末尾有两个0:直接上干货,算法思想如下:对于任意一个正 ...

随机推荐

  1. Eclipse运行错误:Failed to load the JNI shared library的解决办法

    出现上述错误的原因是环境变量配置出问题,查看JAVA_HOME这一环境变量的值是否正确. 操作步骤如下, 1.右键“我的电脑”->属性 ↓ 2.打开“高级系统设置”,如下图: ↓ 3.选择“环境 ...

  2. gstreamer-tips-picture-in-picture-compositing

    http://www.oz9aec.net/index.php/gstreamer/347-more-gstreamer-tips-picture-in-picture-compositing htt ...

  3. linux 网卡buffer大小

    参考截取一部分:https://blog.csdn.net/ysu108/article/details/7764461 在linux下可以修改协议栈改变tcp缓冲相关参数: 修改系统套接字缓冲区 e ...

  4. Python编程-函数进阶

    一.函数对象 函数是第一类对象,即函数可以当作数据传递 1 可以被引用 2 可以当作参数传递 3 返回值可以是函数 4 可以当作容器类型的元素 def foo(): print('from foo') ...

  5. VS中一个强大的功能,将Json或者XML黏贴为类

    有时候需要传递json,或者是json结构复杂,看的杂乱无章,我们可以将这个json复制下来,然后将它写成类的形式,VS中已经帮我们很好的实现了这个功能,我们只需要选择   编辑===>> ...

  6. vRO Extend VirtualDisk Workflow

    https://vbombarded.wordpress.com/2015/02/20/vrealize-orchestrator-extend-virtual-disk-workflow/ var ...

  7. SQL Server technical bulletin - How to resolve a deadlock

    https://support.microsoft.com/en-us/help/832524/sql-server-technical-bulletin-how-to-resolve-a-deadl ...

  8. [转载]spring security 的 logout 功能

    原文地址:security 的 logout 功能">spring security 的 logout 功能作者:sumnny 转载自:http://lengyun3566.iteye ...

  9. sqoop1.4.6 全量导入与增量导入 与使用技巧

    全量导入: sqoop import --connect jdbc:mysql://192.168.0.144:3306/db_blog --username root --password 1234 ...

  10. HIVE 2.1.0 安装教程。(数据源mysql)

    前期工作 安装JDK 安装Hadoop 安装MySQL 安装Hive 下载Hive安装包 可以从 Apache 其中一个镜像站点中下载最新稳定版的 Hive, apache-hive-2.1.0-bi ...