摘要:
本书用来介绍一些基于bit位的算法。充分利用计算机本身的指令,来进行高效的算法。读书笔记摘录了其中的一些算法。读书笔记中的章节跟书中的章节保持一致。
第2章. 对bit位的一些操作
2.1 操作最右侧位
1. 这里有一个基本的定理:
将字映射到字的函数可以用字并行加、减、与、或、非指令实现,当且仅当函数的结果的每一位只依赖于每个输入操作数的相应位以及相应位右侧的位。
2. 将一个字最右侧的1位改成0位
例如 0101 1000 => 0101 0000 : x&(x-1)
3. 检测一个无符号整数是否是2^n -1的形式:
x&(x+1)
4. 析出(isolate)最右侧的1位, 如果没有1位则生成所有位均为0的字
例如 0101 1000 => 0000 1000 : x&(-x)
5. 析出(isolate)最右侧的0位, 如果没有0位则生成所有位均为0的字
例如 0101 0111 => 0000 1000 : ~x&(x+1) ,注意这里第一项是x按位取反
6.再举最后一个复杂一点的,其它的位操作算法就不一一列举了。可以依据定理,只要符合定理的bit操作,都可以用这种方式实现。
将最右侧连续的1改成0. 例如0101 1000 => 0100 0000
((x|(x-1))+1) & x
2.7 符号函数
符号函数的定义是:
[ -1 , x< 0 ]
sign(x) = [ 0 , x=0 ]
[ 1, x>0 ]
1. 最直观的解法是:
(x>0)-(x<0) 或(x>=0)-(x<=0)。
这种方式可以扩展到比较函数
[ -1 , x< y ]
cmp(x,y) = [ 0 , x=y ]
[ 1, x>y ]
(x>y)-(x
2. 用移位指令实现
( (带符号右移)x>>31 ) |((无符号右移)-x>>31)
或者
-(x>>31) | (-x>>31) , 这里都是无符号右移,但对x=-2^31这个边界数会失败。
2.12 如何检测溢出
这里讨论的是如何不使用cpu的“溢出状态位”来检测溢出。因为有些cpu比如MIPS根本就没有这个状态位。这个对于大整数的加减和绝对值运算很重要。后面会讲基于这个方法的大整数(比如128bit, 256bit)的加减算法。
1. 带符号加法的溢出检测
x+y+c, 其中c表示进位,只能是1或者0.
溢出检测: ((x+y+c) xor x) &((x+y+c) xor y)
其结果再符号位给出,可以再其后面加入右移位31位,然后得到1或者0的值。
x-y-c, 其中c表示借位,只能是1或者0。
溢出检测:(x xor y) & ((x-y-c) xor x)
其结果再符号位给出,可以再其后面加入右移位31位,然后得到1或者0的值。
2. 借位和进位的计算
进位:(x+y+c) xor x xor y
进位:(x-y-c) xor x xor y
3. 无符号加减和乘法的溢出这里不详述。具体看书。
2.15 大数字的加减算法
设操作数是(x1,x0)和(y1,y0), 结果是(z1,z0),则大数加法:
z0=x0+y0
c=[ (x0&y0) | ((x0|y0) &~z0)]>>31 (这里计算进位)
z1=x1+y1+c
减法
z0=x0-y0
b=[ (~x0&y0) | ((x0 按位等值 y0)& z0)] >>31 (这里计算补位)
z1=x1-y1-b
这里的算法,基本上比现在网上能查到的算法效率都高。十个左右的指令可以完成。但代价就是可移植性差了。算法没有具体验证,仅仅是从书中摘录。如果需要工程使用请自行验证。
2.16 双字长移位
2.17 多字节大数的加减和绝对值计算
这两个算法不在这里叙述。可以参考书本身。
2.19 交换寄存器
这个就是两个变量互相交换并且不借助第三个变量。
c语言实现可以为:
x=x+y
y=x-y
x=x-y
用bit位操作也能实现
x=x xor y
y=y xor x
x=x xor y
第三章 2的幂边界
3.1 上舍入和下舍入到2的幂的操作
这个就是如何将一个数,如何获取离他最近(大于或者小于)的2的幂的数。
不具体叙述。看书上描述。
第五章位计数
5.1 “1”位计数
统计一个字(比如32bit)中,有多少个bit是1,有多少个bit是0。
这里以32bit为例来说明算法,这里基本思想类似折半计算。算法复杂度O(lgn)
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);
通过优化,还可以减少几个指令。具体优化就看书上介绍。但基本原理就是上面这个。
统计这种个数,这里还有一个查表的算法。就是把8bit的数,做成0-255的表。直接查数组可以算出多少个bit。这个方式在很多情况下都可以很有用。
5.2 奇偶性
统计一个数里面,bit是1的个数是奇数个还是偶数个。
具体算法见书上的说明
5.3 前导零和后缀零的计算
就是计算一个数,有多少bit是前导0,有多少bit是后缀0.
具体算法见书上说明。
第六章字搜索
6.1 搜索第一个为0的字节
这个很有用的一个功能就是找字符串的结尾字符'\0'. 传统的算法就是一个byte一个byte的去寻找是否等于0。书中给出了一个算法,对于64bit的机器,可以节省一半的指令。不具体细述。可以参考strlen的实现,看看strlen如何实现的。
6.2 寻找第一个给定长度的1位串
一般这个问题用来做压缩算法之类的有用。不细述。需要的时候再来查书。
第7章位和字节的重排列
暂时不太关心这种操作,没怎么看。
第8章乘法
第9章除法
第10章整数常量除法
这几章有个很有用的东西,就是大数的乘法和除法。128bit或者256bit大数的乘除如何高效实现。乘法有算法,但除法很麻烦。第十章就是将如何尽量避免使用除法。
具体算法也不在这里细述了。
这里可以看看,开源的加密算法,是如何实现大数的加减乘除的。书上讲的效率很高,但可移植性不好。相信开源的加密算法,对这个问题的处理,会更多考虑可移植性。
第11-16章
是一些数值计算的高效方法。不太关心。没看。
·