关于bit操作,你知道多少?

bit操作是非常trick的东西,通过bit操作,可以高效率的完成许多事情,简单的如判断一个数的奇偶,找一个数最低位的1,返回一个数的二进制表示中1的个数
复杂点的,有位图操作,这个在海量数据处理中经常会用到,还有linux2.6的O(1)的调度算法,也是使用的一个140位的位图,(5个unsigned long)
基本的比特操作:&,|,^,~(按位取反),<<,>>,>>>(循环右移)
复合操作符,如&=、|=、 ^=、<<=、>>=

微软实习生面试时,有一题就是给一堆64位的无符号long表示的长整型数,让你通过一堆比特操作,给出一个最小值。

下面开始简单的题目(以下主要是参考的nash_和MoreWindows的blog)

nash_ 点击打开链接

MoreWindows

 位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错

位操作符的运算优先级比较低,要使用括号

1 int的最大值和最小值(这里是补码表示法)

(1<<31) - 1 = 2147483647 有优先级,所以必须加括号
~(1<<31)= 2147483647
1 << 31 = -2147483648
同理,求long的最大值:((long)1 << 127) -1 

2 与2相关的计算,*2,/2,* 2的m次,/ 2的m次

n << 2
n >> 2
n << m
n >> m


3 判断奇偶性

n % 2 == 0 / 1

x & 1 == 0 / 1因为奇数的最低位为1,偶数的最低位为0


4 交换两个数a、b

a = a^b

b = b^a

a = a^b

^运算满足交换律,一个数和自己异或的结果为0

任何数与0异或都会不变的


5 取绝对值

 n > 0 ? n : - n

(n ^ (n >> 31)) - (n >> 31)

n右移31位,求的是n的符号。如果是正的,n >> 31得0,负数得-1

如果n是正数,n ^ (n>>31) - (n>>31) = n ^ 0 - 0 = n

如果n是负数,n ^ (n >> 31) - (n>>31) = n ^ (-1) - (-1) 

一个负数与-1的异或,结果等于其绝对值-1。

-2 ^ -1 = 1

-3 ^ -1 = 2 

-1 ^ -1 = 0

那么将结果-(-1),相当于+1,就是其绝对值了

或者

(n >> 31) ? n : (~a + 1)


任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反


6 求两个数的最大值、最小值

a > b ? a : b

b & ((a-b) >> 31) | a &  ( ~(a-b) >> 31)

其中,(a-b)右移31位,与上一题一样,正数返回0,负数返回-1

也就是说,如果a >= b,(a-b) >> 31 = 0 ; 否则等于-1

                    如果a >= b,~ (a-b) >> 31 = -1

如果a >= b,原式 = b & 0 | a &  -1 = a

反之一样

-- 最小

a & ((a-b) >> 31 ) | b & (~(a-b) >> 31)


7 判断两个数符号是否相同

a ^ b >= 0 同号


8 判断一个数是否2的幂次

n > 0 ? (n & (n-1) == 0 ) : false


9 求两个数的均值

(x + y ) >> 2

((x ^ y) >> 1 ) + (x & y)

其中,x^y右移1位,得到的是x、y其中一个为1的位,然后除以2

x & y 得到的是x,y都为1的部分。

加起来就是平均数了


10 从低位到高位,取n的第m位

( n >> (m-1) ) & 1 

先右移m-1次,然后跟1做&操作


11 从低位到高位,将m的第n位置1

n | (1 << (m-1))

将1左移 m-1 次,得到 1 00***0,


12 从低位到高位,将m的第n位置0

n & ~( 1 << (m-1)) 取反操作


13 取相反数:

 ~n +1 或者  (n ^ -1 ) + 1


14 高低位交换

二进制数的前8位为“高位”,后8位为“低位”

10000110 11011000  --->  11011000 10000110

只要将x>>8与x<<8这两个数相或就可以了

x >> 8 | x << 8


15 二进制逆序

字符串的逆序/类似于归并排序的分组处理

10000110 11011000 ---> 

00011011 01100001

第一步:每2位为一组,组内高低位交换

      10 00 01 10  11 01 10 00

  -->01 00 10 01 11 10 01 00

第二步:每4位为一组,组内高低位交换

      0100 1001 1110 0100

  -->0001 0110 1011 0001

第三步:每8位为一组,组内高低位交换

      00010110 10110001

  -->01100001 00011011

第四步:每16位为一组,组内高低位交换

      01100001 00011011

  -->00011011 01100001


关于第一步,交换奇偶位的操作

原 数    10000111100

      奇数位 1_0_0_1_ 1_0_1_0_

      偶数位  _0_0_1_0 _1_1_0_0

将下划线用0填充,可得

      原 数    00

      奇数位        偶数位 000100 00

再将奇数位右移一位,偶数位左移一位,此时将这两个数据相或即可以达到奇偶位上数据交换的效果了。

      原 数                  奇数位右移 01000001 01000100  

      偶数位左移 000

      相或得到      01001001 11100100



16 二进制中1的个数

每次右移,& 0x01,看结果是否为1。直到将原数移位成0为止


x & (x -1 ) 会将最低位的1置0

另一种高效的做法

第一步:每2位为一组,组内高低位相加

      10 00 01 10  11 01 10 00

  -->01 00 01 01  10 01 01 00

高低位就是一半、一半(这里就是1位+1位)

 x = ((x & 0xAAAA) >> 1) + (x & 0x5555);


第二步:每4位为一组,组内高低位相加

      0100 0101 1001 0100

  -->0001 0010 0011 0001

这里是两位+两位

 a = ((a & 0xCCCC) >> 2) + (a & 0x3333);


第三步:每8位为一组,组内高低位相加

      00010010 00110001

  -->00000011 00000100

a = ((a & 0xF0F0) >> 4) + (a & 0x0F0F); 


第四步:每16位为一组,组内高低位相加

      00000011 00000100

  -->00000000 00000111

a = ((a & 0xFF00) >> 8) + (a & 0x00FF);  

最后a就是原来的整数x中的1的个数

在Java中不能直接使用二进制表示数字,可以使用8进制或者16进制来间接表示

Integer.toHexString(x)可以输出二进制字符串(不过高位的0没有补足)


17 缺失的数字

  很多成对出现数字保存在磁盘文件中,注意成对的数字不一定是相邻的,如2,3,4,2……,由于意外有一个数字消失了,如何尽快的找到是哪个数字消失了?

将这组数都进行异或。由于X^X = 0

最后剩余的那个数,一定是唯一的数


变体:如果有2个数是唯一出现的,其余都是两两出现的,怎么找出这三个数

不妨设all为所有数异或的结果,all肯定有一位是1,不妨设第i位为1

根据i是否为1,将原先的数分成两堆。分别,进行异或,得到的结果,就是那两个唯一出现的数


如果有三个数是唯一出现的呢?跟两个数唯一出现类似?

问题是,如果根据第i位是否为1 分堆,有可能,选出的i,不能区分这三个数(不能将这三个数分到不同的堆)

1 ^ 1 ^ 1 = 1

1 ^ 1 ^ 0 = 0

1 ^ 0 ^ 0 = 1

这个我再想想

18 位操作与空间压缩,针对筛素数进行空间压缩

这个第一个参考连接中讲了,我没细看

相关文章

财联社10月10日讯(编辑 赵昊)当地时间周二(10月8日),美...
PANews 9月29日消息,币安前首席执行官赵长鹏在X平台发文称:...
凤凰网科技讯 3月11日,比特币报价突破71000美元,创历史新高...
赵长鹏 凤凰网科技讯 北京时间9月28日,据彭博社报道,美国当...
“前华人首富”赵长鹏的出狱时间或再提前。 Binance(币安)...
财联社5月24日讯(编辑 史正丞)当地时间周四盘后,根据一份...