位运算

来源:互联网 发布:大数据挖掘技术 编辑:程序博客网 时间:2024/05/01 02:29

位运算是计算机最擅长的计算方式,尽管从广义上说,位运算仅仅是布尔代数中的一小部分,但是现实中,两者却不能画等号,
根本的原因是计算机中的位运算是多位的,而且是逻辑运算和算术运算混合的,而常规的布尔代数只研究真值和假值的逻辑运算
长期以来,数学专家对位运算是不屑一顾的,认为已经没有什么可研究的了,只有少数的计算机专家才对这个问题无比的着迷,
因为这就是计算机的思考方式.这里说的少数确实是相当的少,在我的印象中,也只有<<Hacker's Delight>>这一本专门的著作,
不过说实在的,就这一本书也更象是一本笔记之类的东西,只有很少的证明,更多的只是结论,这到也是程序员的思考方式:)

计算机只认识两种数字,0和1,由0和1组成的映射就是布尔代数,基本上它相当于
f(x,y)=z;其中x,y,z=0或者1,其中的f就是布尔函数,因为取值范围仅仅是0和1,数学上f也称之为2度布尔函数
因为x可以取0或者1,y也可以取0或者1,x和y的组合是4种00,01,10,11,因为z也可以取0或者1,因此2度布尔函数的组合数目是2的2次方的2次方=16种,
也就是说有16个映射关系,刚好是16进制的从0到F

但是我们熟悉的布尔运算其实只有三种,与,或,非
可以证明仅仅用这三种运算关系就可以表达16种映射关系,数学上这称之为完备集

证明就免了吧,我直接给出测试代码

#include "stdafx.h"
#include <limits>
#include <string>

#include <bitset>
#include <math.h>

using namespace std;

unsigned long toLong(const string str)
{
 return bitset<32>(str).to_ulong();
}

string toString(unsigned long l)
{
 return bitset<32>(l).to_string();
}

typedef unsigned int uint;

uint fun0(uint x, uint y)
{
 return 0;
}

uint fun1(uint x, uint y)
{
 return x & y;
}

uint fun2(uint x, uint y)
{
 return x & ~y;
}

uint fun3(uint x, uint y)
{
 return x;
}

uint fun4(uint x, uint y)
{
 return ~x & y;
}

uint fun5(uint x, uint y)
{
 return y;
}

uint fun6(uint x, uint y)
{
 return x ^ y;
}

uint fun7(uint x, uint y)
{
 return x | y;
}

uint fun8(uint x, uint y)
{
 return ~(x | y);
}

uint fun9(uint x, uint y)
{
 return ~(x ^ y);
}

uint funA(uint x, uint y)
{
 return ~y;
}

uint funB(uint x, uint y)
{
 return x | ~y;
}

uint funC(uint x, uint y)
{
 return ~x;
}

uint funD(uint x, uint y)
{
 return ~x | y;
}

uint funE(uint x, uint y)
{
 return ~(x & y);
}

uint funF(uint x, uint y)
{
 return -1;
}

typedef uint (*pfun)(uint x, uint y);

void test(pfun fun, const string& desc)
{
 printf("%s/n", desc.c_str());
 printf("(1,1)%s/n", toString((*fun)(1, 1)).c_str());
 printf("(1,0)%s/n", toString((*fun)(1, 0)).c_str());
 printf("(0,1)%s/n", toString((*fun)(0, 1)).c_str());
 printf("(0,0)%s/n", toString((*fun)(0, 0)).c_str());
}

int main(int argc, char* argv[])
{
  test(fun0, "fun0 0");
  test(fun1, "fun1 x & y");
  test(fun2, "fun2 x & ~y");
  test(fun3, "fun3 x");
  test(fun4, "fun4 ~x & y");
  test(fun5, "fun5 y");
  test(fun6, "fun6 x ^ y");
  test(fun7, "fun7 x | y");
  test(fun8, "fun8 ~(x | y)");
  test(fun9, "fun9 ~(x ^ y)");
  test(funA, "funA ~y");
  test(funB, "funB x | ~y");
  test(funC, "funC ~x");
  test(funD, "funD ~x | y");
  test(funE, "funE ~(x & y)");
  test(funF, "funF -1");
}

输出结果如下:
fun0 0
(1,1)00000000000000000000000000000000
(1,0)00000000000000000000000000000000
(0,1)00000000000000000000000000000000
(0,0)00000000000000000000000000000000
fun1 x & y
(1,1)00000000000000000000000000000001
(1,0)00000000000000000000000000000000
(0,1)00000000000000000000000000000000
(0,0)00000000000000000000000000000000
fun2 x & ~y
(1,1)00000000000000000000000000000000
(1,0)00000000000000000000000000000001
(0,1)00000000000000000000000000000000
(0,0)00000000000000000000000000000000
fun3 x
(1,1)00000000000000000000000000000001
(1,0)00000000000000000000000000000001
(0,1)00000000000000000000000000000000
(0,0)00000000000000000000000000000000
fun4 ~x & y
(1,1)00000000000000000000000000000000
(1,0)00000000000000000000000000000000
(0,1)00000000000000000000000000000001
(0,0)00000000000000000000000000000000
fun5 y
(1,1)00000000000000000000000000000001
(1,0)00000000000000000000000000000000
(0,1)00000000000000000000000000000001
(0,0)00000000000000000000000000000000
fun6 x ^ y
(1,1)00000000000000000000000000000000
(1,0)00000000000000000000000000000001
(0,1)00000000000000000000000000000001
(0,0)00000000000000000000000000000000
fun7 x | y
(1,1)00000000000000000000000000000001
(1,0)00000000000000000000000000000001
(0,1)00000000000000000000000000000001
(0,0)00000000000000000000000000000000
fun8 ~(x | y)
(1,1)11111111111111111111111111111110
(1,0)11111111111111111111111111111110
(0,1)11111111111111111111111111111110
(0,0)11111111111111111111111111111111
fun9 ~(x ^ y)
(1,1)11111111111111111111111111111111
(1,0)11111111111111111111111111111110
(0,1)11111111111111111111111111111110
(0,0)11111111111111111111111111111111
funA ~y
(1,1)11111111111111111111111111111110
(1,0)11111111111111111111111111111111
(0,1)11111111111111111111111111111110
(0,0)11111111111111111111111111111111
funB x | ~y
(1,1)11111111111111111111111111111111
(1,0)11111111111111111111111111111111
(0,1)11111111111111111111111111111110
(0,0)11111111111111111111111111111111
funC ~x
(1,1)11111111111111111111111111111110
(1,0)11111111111111111111111111111110
(0,1)11111111111111111111111111111111
(0,0)11111111111111111111111111111111
funD ~x | y
(1,1)11111111111111111111111111111111
(1,0)11111111111111111111111111111110
(0,1)11111111111111111111111111111111
(0,0)11111111111111111111111111111111
funE ~(x & y)
(1,1)11111111111111111111111111111110
(1,0)11111111111111111111111111111111
(0,1)11111111111111111111111111111111
(0,0)11111111111111111111111111111111
funF -1
(1,1)11111111111111111111111111111111
(1,0)11111111111111111111111111111111
(0,1)11111111111111111111111111111111
(0,0)11111111111111111111111111111111

看出问题没有?只有最后一位是满足映射关系的,其余的位不是一堆0就是一堆的1,这就是我上面说过的,计算机的位运算是多位的,这其实是个程序员就知道

&,|,~是简单的,但是问题很快就来了,如果x是一个非负整数,那么从位运算的角度来看,x+1是什么?x-1是什么?-x是什么?

答案:
从位运算的角度来看,x+1是寻找最右边的0,并把这个0连同右边的所有位取反
从位运算的角度来看,x-1是寻找最右边的1,并把这个1连同右边的所有位取反
从位运算的角度来看,-x是寻找最右边的1,并把这个1左边的所有位取反

有点不对称啊,是少了一种
从位运算的角度来看,~(x+1)是寻找最右边的0,并把这个0左边的所有位取反

嗯,看起来,从位运算的角度来看,x+1和x-1是相对的,但是-x好像和~(x+1)差得很远啊,其实这里确实有一个恒等式
-x = ~(x-1)
因此第三条也可以说:
从位运算的角度来看,~(x-1)是寻找最右边的1,并把这个1左边的所有位取反,x+1和x-1果然是相对的

在深入下去,既然我们知道了x-1的含义,那么x & (x-1)又是什么意思呢?
嗯,既然x-1是寻找最右边的1,并把这个1连同右边的所有位取反,那么x & (x-1)的含义就是寻找最右边的1,然后对左边和右边做&运算,
当然左边相当于a & a = a(a取0或者1),右边相当于a & ~a = 0(a取0或者1),结果就是把最右边的1变成0了

同理,你还可以分析其余类似的结论,但是我们程序员的思考方式从来都是喜欢简单直接的,还是别绕来绕去的了,直接记住以下的结论吧

寻找最右边的1,而左边不变的公式:
abcd1000变成abcd1000,用公式x
abcd1000变成abcd0111,用公式x-1
abcd1000变成abcd1111,用公式x | (x-1)
abcd1000变成abcd0000,用公式x & (x-1)

寻找最右边的0,而左边不变的公式:
abcd0111变成abcd1000,用公式x+1
abcd0111变成abcd0111,用公式x
abcd0111变成abcd1111,用公式x | (x+1)
abcd0111变成abcd0000,用公式x & (x+1)

记住,从位运算的角度来看,x-1和x+1,&和|,1和0是相对的,因此上面的公式其实也不难记

但是等等,记住这些东西有用吗?嗯,莫须有吧,这取决与我们面对的问题,比如说:
如何判断一个无符号整数是否是2的n次幂的形式?也就是0,1,2,4,8,16...
2的n次幂从位运算的角度来看就是所有位都是0或者只有一位是1的形式,可以利用公式
if (x & (x-1) == 0)来判断,把最右边的1改成0后只剩下0了当然可以保证只有一位是1了,而所有位都是0的情况,无论和谁做&运算当然还是0了
是不是很巧妙呢?

扩展开去,如何判断一个无符号整数是否是2的n次幂-1的形式?限时2分钟给出公式

答案:
很简单,如果x是2的n次幂-1的形式,那么x+1不就是2的n次幂形式嘛,把x+1代入上面的公式:
(x+1) & x

再问如何一个无符号整数是否是2的n次幂+a的形式?不用说了,直接把x-a代入公式就是了:
(x-a) & (x-a-1)

上面说的都是左边不变的情况,如果我们让左边都变成0呢?限时5分钟给出公式

答案:
其实还是很简单,就是把左边不变的公式中的x替换为x & -x就可以了,又忘了从位运算的角度来看,-的含义了吧,
还记得-x=~(x-1)的公式吗?这里的-x也可以写成~(x-1)
x & -x可以保证寻找最右边的1,然后左边全是0,而右边不变
寻找最右边的1,而左边全为0的公式:
abcd1000变成00001000,用公式x & -x
abcd1000变成00000111,用公式(x & -x) -1
abcd1000变成00001111,用公式((x & -x) | ((x & -x)-1))
abcd1000变成00000000,用公式((x & -x) & ((x & -x)-1))

同样的,寻找最右边的0,这次是用x & ~(x+1)替换x,嗯,记住x-1和x+1是对称的
寻找最右边的0,而左边全为0的公式:
abcd0111变成00001000,用公式(x & ~(x+1)) + 1
abcd0111变成00000111,用公式(x & ~(x+1))
abcd0111变成00001111,用公式((x & ~(x+1)) | ((x & ~(x+1))+1))
abcd0111变成00000000,用公式((x & ~(x+1)) & ((x & ~(x+1))+1))

这些公式也太长了,难道就不能化简吗,就像布尔代数中求最简式的算法一样?
不要问我,逻辑和算术的混合运算,单纯的布尔逻辑公式已经失效了,我是无法给出证明和推导的,那位数学方面的高手可以试一试,
不要忘了把结论告诉我:) 

原创粉丝点击