你和dalao之间,也许就差这么几个Magic Number了

SuperJvRuo

2018-02-04 13:30:42

Personal

当第一次看到某些Magic Number时的感觉是 * 这是什么? * 这真的能正常工作么? * 为什么会这样? 这些数字看起来就像魔法一样,通过一种奇妙的方式,完成了一件不可能的事,所以也称它们为 magic number。 虽然在实践中它们未必全部都是实用的,甚至有些可以仅仅归结到有趣中,但其中体现的基础位运算、数值计算和一些基本数学思想是通用的。 ### 1、快速求平方根倒数 * magic number - 0x5f375a86 输入一个单精度浮点数 a 返回结果为 1 / sqrt(a) 比如输入 4.0 ,返回 0.499154 (准确值应是 0.5) 这段代码是编程界广为流传的一段奇妙代码,能估算出单精度浮点数的平方根倒数,它的速度是 cmath 中 1.0 / sqrt(a) 的三倍(当然,误差也更大)。这段代码的基础是牛顿迭代,最令人疑惑的是 0x5f375a86 - (ii >> 1) 这个语句,我们先把这个语句放在一边,过一遍基础流程。 ``` float r_sqrt(float a) { union { int ii; float i; }; //关于共用体union: //结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员共用同一段内存,修改一个成员会影响其余所有成员。 //共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。 i = a; float ihalf = 0.5 * i; //这条语句目的是找到一个较好的 1 / sqrt(a) 的初始估算值,作为牛顿迭代的第一个值 ii = 0x5f375a86 - (ii >> 1); //第一次迭代 i = i * (1.5f - ihalf * i * i); //后续第二次、第三次迭代,实际上如果精度要求不高,一次迭代即可 //i = i * (1.5f - ihalf * i * i); //i = i * (1.5f - ihalf * i * i); return i; } ``` 对于想知道原理的OIer,强烈推荐这篇文章: [FAST INVERSE SQUARE ROOT](http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf) ### 2、奇偶性统计 大多数OIer使用的奇偶性统计: ``` int bits_parity(unsigned int a) { x^=x>>1; x^=x>>2; x^=x>>4; x^=x>>8; x^=x>>16; } ``` 其实还可以优化一下: * magic number - 0x6996 该函数返回 32 位二进制数中 1 的奇偶性。 返回 1 代表存在 奇数个 1,返回 0 代表存在 偶数个 1。 ``` unsigned int parity(unsigned int i) { /* * 每 8 位为一组,统结果存放在每组右侧 4 位元上 * i = 0x83d12312 / 10000011110100010010001100010010 * 此步完成后 i = xxxx1011xxxx1100xxxx0001xxxx0011 */ i = i ^ (i >> 4); /* * 每 16 位为一组,结果存放在每组右侧 4 位元上 * 此步完成后 i = xxxxxxxxxxxx0111xxxxxxxxxxxx0010 */ i = i ^ (i >> 8); /* * 每 16 位为一组,结果存放在每组右侧 4 位元上 * 此步完成后 i = xxxxxxxxxxxxxxxxxxxxxxxxxxxx0101 */ i = i ^ (i >> 16); /* * 0110100110010110 == 0x6996 * 合并i = i ^ (i >> 1)、i = i ^ (i >> 2)两步 */ return (0x6996 >> (i & 0xf)) & 0x1; } ``` ### 3、位元反序 首先说一下大多数OIer使用的反转,使用分治思想。 ``` unsigned int rev(unsigned int x) { x = (x & 0x55555555) << 1 | (x >> 1 & 0x55555555); x = (x & 0x33333333) << 2 | (x >> 2 & 0x33333333); x = (x & 0x0F0F0F0F) << 4 | (x >> 4 & 0x0F0F0F0F); x = (x & 0x00FF00FF) << 8 | (x >> 8 & 0x00FF00FF); x = (x & 0x0000FFFF) << 16 | (x >> 16 & 0x0000FFFF); return x; } ``` Donald E. Knuth 版位元反序算法, * magic number - 0x003f801f、0x01c003e0、0x0e038421、0x11c439ce、0x22488842、0x549556b5 这个算法通过精心设计的位移顺序和掩码实现了位元反序。 输入一个 32 位二进制数,返回 32 位二进制数,其位顺序和输入相反。 ``` //假设i = 01234567 89abcdef ghijklmn opqrstuv unsigned int revert(unsigned int i) { //循环左移 15 位 //fghijklm nopqrstu v0123456 789abcde i = i >> 17 | i << 15; /* * (i & 0x003f801f) << 10 => pqrstuv..........abcde.......... * (i & 0x01c003e0) => .......mno............56789..... * (i >> 10) & 0x003f801f => ..........fghijkl..........01234 * => pqrstuvm nofghijk labcde56 78901234 */ i = (i & 0x003f801f) << 10 | (i & 0x01c003e0) | ((i >> 10) & 0x003f801f); /* * (i & 0x0e038421) << 4 => tuv.......jkl....e....9....4.... * (i & 0x11c439ce) => ...s...mno...i....bcd..678..123. * (i >> 4) & 0x0e038421 => ....pqr.......fgh....a....5....0 * => tuvspqrm nojklifg hebcda96 78541230 */ i = (i & 0x0e038421) << 4 | (i & 0x11c439ce) | ((i >> 4) & 0x0e038421); /* * (i & 0x22488842) << 2 => v...r..o..l...h...d....8....3... * (i & 0x549556b5) => .u.s.q..n..k.i.g.e.c.a9.7.54.2.0 * (i >> 2) & 0x22488842 => ..t...p..m..j...f...b....6....1. * => vutsrqpo nmlkjihg fedcba98 76543210 */ i = (i & 0x22488842) << 2 | (i & 0x549556b5) | ((i >> 2) & 0x22488842); return i; } ```