HACKER'S DELIGHT[算法心得]笔记

第一章  概述
略.

第二章  基础知识

2.1 操作最右侧的位元
x & (x - 1) 将最右侧置位的比特位置零, 该表达式可用来判断x是否为2的幂.
x | (x + 1) 将最右侧置零的比特位置位.
x & (x + 1) 将最右位起始的连续的1比特位置零, 如果最右位非1则不变, 该表达式可用来判断x是否为2^n - 1.
x | (x - 1) 将最右位起始的连续的0比特位置位, 如果最右位非0则不变.
~x & (x + 1) 将最右侧置零的比特位置位, 并将其余位置零.
~x | (x - 1) 将最右侧置位的比特位置零, 并将其余位置位.
~x & (x - 1) 将最右位起始的连续的0比特位置位, 并将其余位置零.
~x | (x + 1) 将最右位起始的连续的1比特位置零, 并将其余位置位.
x & (-x) 保留最右侧位置的比特位, 并将其余位置零.
x ^ (x - 1) 将最右侧置位的比特位及其右侧所有置零的比特位置位, 并将左侧的比特位置零.
x ^ (x + 1) 将最右侧置零的比特位及其右侧所有置位的比特位置位, 并将左侧的比特位置零.
德摩根定律的推论
~(x & y) = ~x | ~y
~(x | y) = ~x & ~y
~(x + y) = ~x - y (特例y = 1时有~(x + 1) = ~x - 1)
~(x - y) = ~x + y (特例y = 1时有~(x - 1) = ~x + 1)
~(x - y) = ~x + y (特例x = 0时有~(0 - y) = y - 1)

2.2 结合逻辑操作的加减运算
-x = ~x + 1 = ~(x - 1)
~x = -x - 1
-(~x) = x + 1
~(-x) = x - 1
x + y = x - (~y) - 1 = (x ^ y) + 2(x & y) = (x | y) + (x & y) = 2(x | y) - (x ^ y)
x - y = x + (~y) + 1 = (x ^ y) - 2((~x) & y) = (x & (~y)) - ((~x) & y) = 2(x & (-y)) - (x ^ y)
x ^ y = (x | y) - (x & y)
x & (~y) = (x | y) - y = x - (x & y)
~(x - y) = y - x - 1 = ~x + y
x @ y = (x & y) - (x | y) - 1 = (x & y) + ~(x | y) (@为同或, 打不出对应符号用@代替, 下同)
x | y = (x & ~y) + y
x & y = ((~x) | y) - (~x)

2.3 逻辑与算术表达式中的不等式
如果将x, y视为无符号整数则必然有以下不等式:
(x ^ y) <= (x | y)
(x & y) <= (x @ y)
(x | y) >= max(x, y)
(x & y) <= min(x, y)
(x | y) <= x + y (若右值未溢出)
(x | y) > x + y (若右值溢出)
|x - y| <= (x ^ y)

2.4 绝对值函数
设y = x >> 31(符号扩展), 则x的绝对值(abs)可通过以下表达式计算:
(x ^ y) - y
(x + y) ^ y
x - (2x & y)
nabs可通过以下表达式计算:
y - (x ^ y)
(y - x) ^ y
(2x & y) - x
若CPU能快速计算一个数与(+/-)1的乘积, 可以使用((x >> 30) | 1) * x

2.5 计算两无符号数的平均值(保证无溢出)
(x & y) + ((x ^ y) >> 1) (x & y为两者相同部分, x ^ y为两者差异部分)
(x | y) - ((x ^ y) >> 1) (x | y即x & y加上x ^ y)

2.6 符号扩展
符号扩展(sign extension)的含义是在比特流中确定某个位元作为符号位并将其值向左传播覆盖左侧位元的原有值.
以第七位作为符号位为例:
((x + 0x80) & 0xFF) - 0x80
((x & 0xFF) ^ 0x80) - 0x80
(x & 0x7F) - (x & 0x80)

2.11 将值为0的位段解码为2的n次方
有些时候值为0是无意义的, 此时可以将0表示为2^n, 解码时需将其转换为2^n, i.e. ((x - 1) & 0xFF) + 1 (使用0表示256).

2.20 互换寄存器中的值
不使用额外空间互换两个寄存器的值
x = x ^ y
y = x ^ y
x = x ^ y
更进一步, 根据掩码m交换两个寄存器中对应的位
x = x ^ y
y = y ^ (x & m)
x = x ^ y

2.22 布尔函数分解式
本文的布尔函数是指自变量为布尔值且函数值亦为布尔值的函数.
定理: 若f(x, y, z)为有三个自变量的布尔函数, 则其可分解为g(x, y) ^ zh(x, y)的形式, 其中g(x, y)与h(x, y)分别为带两个自变量的布尔函数.

第三章  2的幂边界

3.1 将x上调/下调为2的已知次幂的倍数
下调: x & (-2^n)
上调: (x + 2^n - 1) & (-2^n)

3.2 将x上调/下调到2的幂
下调: 核心思想是将最左的置位的位元向右传播.

1 unsigned floor(unsigned x)
2 {
3     x = x | (x >> 1);
4     x = x | (x >> 2);
5     x = x | (x >> 4);
6     x = x | (x >> 8);
7     x = x | (x >> 16);
8     return x - (x >> 1);
9 }

 


上调: 类似下调算法.

 1 unsigned ceiling(unsigned x)
 2 {
 3     x = x - 1;
 4     x = x | (x >> 1);
 5     x = x | (x >> 2);
 6     x = x | (x >> 4);
 7     x = x | (x >> 8);
 8     x = x | (x >> 16);
 9     return x + 1;
10 }

 

第四章  算术边界
略.

第五章  位计数

5.1 统计值为1的位元个数
分治法(Divide&Conquer):
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
上式为原型式, 还有以下写法:

1 int pop(unsigned x)
2 {
3     x = x - ((x >> 1) & 0x55555555);
4     x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
5     x = (x + (x >> 4)) & 0x0F0F0F0F;
6     x = x + (x >> 8);
7     x = x + (x >> 16);
8     return x & 0x0000003F;
9 }

 

5.2 字组的奇偶性(统计字组中值为1的位元个数的奇偶)
y = x ^ (x >> 1);
y = y ^ (y >> 2);
y = y ^ (y >> 4);
y = y ^ (y >> 8);
y = y ^ (y >> 16);

5.3 前导零的计数(nlz, number of leading zero)
太多了, 循环, 二分, 查表, 不记了. 还有一种利用浮点数计算前导零个数:

 1 int nlz(unsigned k)
 2 {
 3     union {
 4         unsigned asInt[2];
 5         double asDouble;
 6     };
 7     int n;
 8     asDouble = (double)k + 0.5;
 9     n = 1054 - (asInt[LE] >> 20); //小端字节序LE为1, 大端字节序LE为0
10     return n;
11 }


比较字组前导零个数
一种无需计算nlz的方式:
nlz(x) == nlz(y)当且仅当(x ^ y) <= (x & y)
nlz(x) < nlz(y)当且仅当(x & ~y) > y
nlz(x) <= nlz(y)当且仅当(y & ~x) <= x

5.4 后缀零(ntz, number of tail zero)的计数
32 - nlz(~x & (x - 1))
还有一种[Gaud]算法:

 1 int ntz(unsigned x)
 2 {
 3     unsigned y, bz, b4, b3, b2, b1, b0;
 4     y = x & -x; //找到最后侧非零比特并将其左侧所有比特置零
 5     bz = y ? 0 : 1;
 6     b4 = (y & 0x0000FFFF) ? 0 : 16;
 7     b3 = (y & 0x00FF00FF) ? 0 : 8;
 8     b2 = (y & 0x0F0F0F0F) ? 0 : 4;
 9     b1 = (y & 0x33333333) ? 0 : 2;
10     b0 = (y & 0x55555555) ? 0 : 1;
11     return bz + b4 + b3 + b2 + b1 + b0;
12 }

 


理解上面的算法后可以更进一步[Seal]算法: 核心思想是将x的可能性从2 ^ 32压缩到一个稠密集合(dense set), 然后查表.

 1 int ntz(unsigned x)
 2 {
 3     static char table[64] =
 4     {
 5         32, 0, 1, 12, 2, 6, u, 13,
 6         3, u, 7, u, u, u, u, 14,
 7         10, 4, u, u, 8, u, u, 25,
 8         u, u, u, u, u, 21, 27, 15,
 9         31, 11, 5, u, u, u, u, u,
10         9, u, u, 24, u, u, 20, 26,
11         30, u, u, u, u, 23, u, 19,
12         29, u, 22, 18, 28, 17, 16, u
13     }; //u为未定义值
14     //(x & -x)后必然是2的幂, 因此相当于对0x0450FBAF执行移位操作
15     //选取0x0450FBAF是因为可以使用移位操作(0x0450FBAF = 17 * 65 * 65535)代替大数乘法, 更进一步优化算法
16     x = (x & -x) * 0x0450FBAF;
17     return table[x >> 26];
18 }

 


使用德布鲁因循环序列的算法:
德布鲁因序列为0000 0100 1101 0111 0110 0101 0001 1111

 1 int ntz(unsigned x)
 2 {
 3     static char table[32] =
 4     {
 5         0, 1, 2, 24, 3, 19, 6, 25,
 6         22, 4, 20, 10, 16, 7, 12, 26,
 7         31, 23, 18, 5, 21, 9, 15, 11,
 8         30, 17, 8, 14, 29, 13, 28, 27
 9     };
10     if (x == 0)
11         return 32;
12     x = (x & -x) * 0x04D7651F; //0x04D7651F正好为((2047 * 5 * 256 + 1) * 31)
13     return table[x >> 27];
14 }

 

第六章  在字组中搜索位串
略.

 

第七章  重排位元与字节

7.1 反转位元与字节
反转位元: 可利用位计数章节中的种群算法
x = (x & 0x55555555) << 1 | (x & 0xAAAAAAAA) >> 1;
x = (x & 0x33333333) << 2 | (x & 0xCCCCCCCC) >> 2;
x = (x & 0x0F0F0F0F) << 4 | (x & 0xF0F0F0F0) >> 4;
x = (x & 0x00FF00FF) << 8 | (x & 0xFF00FF00) >> 8;
x = (x & 0x0000FFFF) << 16 | (x & 0xFFFF0000) >> 16;

7.2 乱序排列位元
x = (x & 0x0000FF00) << 8 | (x >> 8) & 0x0000FF00 | x & 0xFF0000FF;
x = (x & 0x00F000F0) << 4 | (x >> 4) & 0x00F000F0 | x & 0xF00FF00F;
x = (x & 0x0C0C0C0C) << 2 | (x >> 2) & 0x0C0C0C0C | x & 0xC3C3C3C3;
x = (x & 0x22222222) << 1 | (x >> 1) & 0x22222222 | x & 0x99999999;

第八章  乘法
略.

第九章  整数除法
略.

第十章  除数为常量的整数除法
略.

第十一章  初等函数

11.1 整数平方根
牛顿法开平方

 1 int isqrt(unsigned x)
 2 {
 3     unsigned x1;
 4     int s, g0, g1;
 5     
 6     if (x <= 1)
 7         return x;
 8     s = 1;
 9     x1 = x - 1;
10     if (x1 > 65535)
11     {
12         s = s + 8;
13         x1 = x1 >> 16;
14     }
15     if (x1 > 255)
16     {
17         s = s + 4;
18         x1 = x1 >> 8;
19     }
20     if (x1 > 15)
21     {
22         s = s + 2;
23         x1 = x1 >> 4;
24     }
25     if (x1 > 3)
26     {
27         s = s + 1;
28     }
29     g0 = 1 << s;j
30     g1 = (g0 + (x >> s)) >> 1;
31     
32     while (g1 < g0)
33     {
34         g0 = g1;
35         g1 = (g0 + (x / g0)) >> 1;
36     }
37     return g0;
38 }

 


二分查找

 1 int isqrt(unsigned x)
 2 {
 3     unsigned a, b, m;
 4     a = 1;
 5     b = (x >> 5) + 8;
 6     is (b > 65535)
 7         b = 65535;
 8     do
 9     {
10         m = (a + b) >> 1;
11         if (m * m > x)
12             b = m - 1;
13         else
14             a = m + 1;
15     } while (b >= a);
16     return a - 1;
17 }

 

11.2 整数立方根

 1 int icbrt(unsigned x)
 2 {
 3     int s;
 4     unsigned y, b;
 5     y = 0;
 6     for (s = 30; s >= 0; s = s - 3)
 7     {
 8         y = 2 * y;
 9         b = (3 * y * (y + 1) + 1) << s;
10         if (x >= b)
11         {
12             x = x - b;
13             y = y + 1;
14         }
15     }
16     return y;
17 }

 

11.4 整数对数
以2为底的整数对数:
log2(x) = 31 -nlz(x)
注意当x = 0时定义为-1(虽然数学意义上不成立), 方便运算.
以10为底的整数对数:

 1 int ilog10(unsigned x)
 2 {
 3     int i;
 4     static unsigned table[11] = 
 5     {
 6         0, 9, 99, 999, 9999,
 7         99999, 999999, 9999999, 99999999, 99999999, 0xFFFFFFFF
 8     };
 9     for (i = -1; ; i++)
10     {
11         if (x <= table[i + 1])
12             return i;
13     }
14 }

 

第十二章  以特殊值为底的数制
略.

第十三章  格雷码
略.

第十四章  循环冗余校验

原理: 多项式整除

 1 unsigned crc32(unsigned char *msg)
 2 {
 3     int i, j;
 4     unsigned byte, crc;
 5     i = 0;
 6     crc = 0xFFFFFFFF;
 7     while (msg[i] != 0)
 8     {
 9         byte = msg[i];
10         byte = reverse(byte); //reverse byte order
11         for (j = 0; j <= 7; j++)
12         {
13             if ((int)(crc ^ byte) < 0)
14                 crc = (crc << 1) ^ 0x04C11DB7; //CRC32
15             else
16                 crc = crc << 1;
17             byte = byte << 1;
18         }
19         i++;
20     }
21     return reverse(-crc);
22 }


查表法

 1 unsigned crc32(unsigned char *msg)
 2 {
 3     int i, j;
 4     unsigned byte, crc, mask;
 5     static unsigned table[256];
 6     /* set up table if necessary */
 7     if (table[1] == 0)
 8     {
 9         for (byte = 0; byte <= 255; byte++)
10         {
11             crc = byte;
12             for (j = 7; j >= 0; j--)
13             {
14                 mask = -(crc & 1);
15                 crc = (crc >> 1) ^ (0xEDB88320 & mask);
16             }
17             table[byte] = crc;
18         }
19     }
20     /* calculate CRC */
21     i = 0;
22     crc = 0xFFFFFFFF;
23     while ((byte = msg[i]) != 0)
24     {
25         crc = (crc >> 8) ^ table[(crc ^ byte) & 0xFF];
26         i++;
27     }
28     return -crc;
29 }

 

第十五章  纠错码
略.

第十六章  希尔伯特曲线
略.

第十七章  浮点数

17.1 IEEE格式
单精度(single)格式: s(1 bit) e(8 bit) f(23bit).
从高到低分别为符号位, 指数位, 小数位. 当e与f取不同值时有:
e = 0, f = 0, 数值(+/-)0.
e = 0, f != 0, 数值(+/-)(2 ^ -126)(0, f).
0 < e < 255, 数值(+/-)(2 ^ (e - 127))(1, f).
e = 255, f = 0, 数值(+/-)无穷.
e = 255, f != 0, 数值NaN(not a number).
i.e. 将pi编码为单精度浮点格式: pi = 11.001010000111111011010101000100010000101b.
符号位为0, 指数位为128, 小数位为10010010000111111011011, 即0 1000 0000 1001 0010 0001 1111 1011 011, 十六进制为0x40490FDB.
对于单精度浮点数而言, 最后一位(unit in the last position, ulp)在1 / (2 ^ 24)到1 / (2 ^ 23)之间, 这也是单精度浮点数的精度.
因此单精度浮点数可精确表示的整数范围是-2 ^ 24到+2 ^ 24, 大于此范围的部分整数也可以精确表示.
双精度(double)格式类似单精度, 只是位数区别: s(1 bit) e(11 bit) f(52bit).

17.4 估算平方根倒数

 1 float rsqrt(float x0)
 2 {
 3     union
 4     {
 5         int ix;
 6         float x;
 7     };
 8     x = x0;
 9     float xhalf = 0.5f * x; //或直接指数减1: int ihalf = ix - 0x00800000;
10     ix = 0x5F375A82 - (ix >> 1); //初始猜测值
11     x = x * (1.5f - xhalf * x * x); //牛顿法
12     return x;
13 }


原理: 设x = 2 ^n * (1 + f), 则平方根倒数y = 2 ^ (-n / 2) * (1 + f) ^ (-1 / 2).
因x的指数位127 + n, y的指数为127 - n / 2 = 190.5 - (127 + n) / 2, 即将x右移一位后从190中减去就可估算y值.
求得第一次猜测值后再用牛顿法求解.

第十八章  素数公式
完全看不懂, 略...

 

posted @ 2018-02-22 15:20  Five100Miles  阅读(3345)  评论(0编辑  收藏  举报