位运算

来源:互联网 发布:淘宝客服怎么自动回复 编辑:程序博客网 时间:2024/05/21 04:02

1 找出一个整数的二进制表示位中1的个数
如:36 100100,其中有2个’1’。
首先来看一个最牛的算法,没有跳转,可以并行处理的:

define POW(c) (1<<(c))

define MASK(c) (((unsigned long)-1) / (POW(POW(c)) + 1))

define ROUND(n, c) (((n) & MASK(c)) + ((n) >> POW(c) & MASK(c)))

int bit_count(unsigned int n)
{
n = ROUND(n, 0);
n = ROUND(n, 1);
n = ROUND(n, 2);
n = ROUND(n, 3);
n = ROUND(n, 4);
return n;
}
其实是分治算法的应用:
1 0 1 1 1 1 0 0 0 1 1 0 0 0 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1
0 1
1 0
1 0
0 0
0 1
0 1
0 0
1 0
0 1
1 0
1 0
0 1
1 0
1 0
1 0
1 0
0 0 1 1
0 0 1 0
0 0 1 0
0 0 1 0
0 0 1 1
0 0 1 1
0 1 0 0
0 1 0 0
0 0 0 0 0 1 0 1
0 0 0 0 0 1 0 0
0 0 0 0 0 1 1 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0
1位数目计数的好方法是,首先设置每个2位字段为原来的两个位的和,然后,求相临2位字段的和,把结果放入相应的4位字段,以此类推.应此,对32位无符号整数,实际上要做的是
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);
上面的算法中:
MASK(0) = 0x55555555 = 01010101010101010101010101010101 b
MASK(1) = 0x33333333 = 00110011001100110011001100110011 b
MASK(2) = 0x0f0f0f0f = 00001111000011110000111100001111 b
MASK(3) = 0x00ff00ff = 00000000111111110000000011111111 b
MASK(4) = 0x0000ffff = 00000000000000001111111111111111 b

ROUND中对n的处理:(n & MASK) + (n >> POW & MASK)

普通的算法
int bit_count(unsigned int n)
{
int count;
for(count = 0; n; n >>= 1)
{
count += n & 1;
}
return count;
}
int bit_count(unsigned int n)
{
int count;
for(count = 0; n; n &= n - 1)
{
count++;
}
return count;
}
解析:
如何只数’1’的个数?如果一个数字至少包含一个’1’位,那么这个数字减1将从最低位开始依次向高位借位,直到遇到第一个不为’0’的位。依次借位使得经过的位由原来的’0’变为’1’,而第一个遇到的那个’1’位则被借位变为’0’。
36 d = 100100 b
36-1 d = 100011 b
如果最低位本来就是’1’,那么没有发生借位。
现在把这2个数字做按位与:n & n-1的结果:
36 & 36-1 d = 100000 b去掉了后面的1。
现在再做一次100000 & 011111 = 0。所有的1都去掉。最后计数出来就是1的个数。
n&(n-1)还有一个妙用,用于判断一个数是否是2的幂. 其特点就是 只有最高位为1,其余位都为0. 1特殊为2的0次幂.0要排除.
则n&(n-1) == 0 即为2的n次幂
2 位运算的一些用法
用位运算实现两个数的交换
x ^=y;
y^=x;
x^=y;
原因:两次异或运算即为原值.
求绝对值:
int abs( int x )
{
int y ;
y = x >> 31 ;
return (x^y)-y ; //or: (x+y)^y
}
3 位运算与大小端问题
用宏实现小端到大端格式的转换:
unsigned long int htonl(unsigned long int hostlong) {

if __BYTE_ORDER==__LITTLE_ENDIAN

return (hostlong>>24) | ((hostlong&0xff0000)>>8) |
((hostlong&0xff00)<<8) | (hostlong<<24);

else

return hostlong;

endif

}

linux 中对16位, 32位高低调换的做法:

define __bswap_constant_16(x) \

 ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))

define __bswap_constant_32(x) \

 ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) |       \  (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))

嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
0x4000
0x4001
存放内容
0x34
0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址
0x4000
0x4001
存放内容
0x12
0x34
32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
0x4000
0x4001
0x4002
0x4003
存放内容
0x78
0x56
0x34
0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址
0x4000
0x4001
0x4002
0x4003
存放内容
0x12
0x34
0x56
0x78
判断大小端的方法:
1) 联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。
int checkCPU( )
{
union w
{
int a;
char b;
} c;
c.a = 1;
return(c.b ==1);
}
返回真:小端格式,否则大端格式.
short int x;
char x0,x1;
x=0x1122;
x0=((char*)&x)[0]; //低地址单元
x1=((char*)&x)[1]; //高地址单元
若x0=0x11,则是大端; 若x0=0x22,则是小端……

0 0
原创粉丝点击