1. Java支持的位运算符:
&:按位与
|:按位或
~:按位非(Esc下面那个键)
^:按位异或
<<:左位移运算符
>>:有符号右位移运算符
>>>:无符号右位移运算符
因为Java在表示二进制数时采用的是补码(2's complement)的形式,最高位为符号位,即负数为1,非负数为0。
无符号右位移运算会直接在高位补0,而有符号右位移运算符会根据当前符号位的数字决定高位补0或1以保证结果正负性不变。
2.位运算小技巧
1).求整数m与2的n次幂的乘积,即m*(2^n)
可直接使用 m<<n 获得结果,如:
int a = 7<<2 //a = 7*(2^2) = 7*4 = 28
int b = 6<<1 //b = 6*(2^1) = 6*2 = 12
注意,任何数左(右)位移32位或32的整数倍位所得的结果与原数字相同
2).求整数m对2的n次幂取余的结果,即m%(2^n)
可直接使用 m&((1<<n)-1) 获得结果,如:
int a = 19&((1<<4)-1) //a = 19%(2^4) = 19%16 = 3
3).判断整数m的奇偶性,即m%2==1?奇数:偶数
可直接使用 (m&1)==1?奇数:偶数 获得结果,如:
boolean a = (3&1)==1 //true
boolean b = (4&1)==1 //false
4).不用临时变量交换两个整数的值
连续使用三次异或,获得结果,如:
int a = 3, b = 4
a = a^b
b = a^b // b = 3
a = a^b // a = 4
原理:
异或0具有保持的特点,即1010^0000 = 1010;
异或1具有翻转的特点,即1010^1111 = 0101;
由此可推导:
b^(a^b) = a
a^(b^(a^b)) = b
5).取整数m的绝对值,即Math.abs(m)
可直接使用 (m^(m>>31))-(m>>31) 获得结果,如:
int a = (-3^(-3>>31))-(-3>>31) //a=3
int b = (5^(5>>31))-(5>>31) //b=5
原理:
m>>31可取出整数m的最高位,即符号位。
若符号位为0,则m为非负数,m异或0,m不变,再减去0,m仍不变
若符号位为1,则m为负数,m异或1,m每位翻转,再减去1,根据补码的定义,负数m变为正数-m
6).将二进制码右数第一个1输出
直接通过 a&(~a)获得结果,原理如下:
a = 00110100
~a = 11001011
-a = 11001100
a & -a = 00000100
「-a」其实是个算术运算,它等于把 a 取反再加 1。观察上面的例子,~a 的各位均与 1 相反,给它加上 1,会导致一系列进位,使得从最低位开始的一串 1 变成 0,而这一串 1 前面的 0 变成 1。比较 a 与 -a 可以发现,它们有且仅有一位同时为 1,而这个 1 恰好是 a 中最右边一个 1 的位置,于是 a & -a 就可以把这个 1 提取出来了。
7).从最低位遍历二进制码中所有的1
在6).的基础上每次取出1后通过异或将该位置0,即可取出下一个1。注意这个过程会改变a的值。
while (a != 0){
p = a & -a
a ^= p
Do something with p
}
8).判断二进制码中1的个数的奇偶性
你能看出这个代码的原理吗?
x ^= (x >>> 1);
x ^= (x >>> 2);
x ^ =(x >>> 4);
x ^= (x >>> 8);
x ^= (x >>> 16);
为了说明上面这段代码的原理,我们拿数字1314520出来说事。1314520的二进制为101000000111011011000,第一次异或操作的结果如下:
00000000000101000000111011011000
^ 0000000000010100000011101101100
—————————————
00000000000111100000100110110100
得到的结果是一个新的二进制数,其中右起第i位上的数表示原数中第i和i+1位上有奇数个1还是偶数个1。比如,最右边那个0表示原数末两位有偶数个1,右起第3位上的1就表示原数的这个位置和前一个位置中有奇数个1。对这个数进行第二次异或的结果如下:
00000000000111100000100110110100
| 000000000001111000001001101101
—————————————
00000000000110011000101111011001
结果里的每个1表示原数的该位置及其前面三个位置中共有奇数个1,每个0就表示原数对应的四个位置上共偶数个1。一直做到第五次异或结束后,得到的二进制数的最末位就表示整个32位数里有多少个1,这就是我们最终想要的答案。
9).计算二进制码中的1的个数
本条参考:https://www.douban.com/note/274239939/
同样假设x是一个32位整数。经过下面五次赋值后,x的值就是原数的二进制表示中数字1的个数。比如,初始时x为1314520,那么最后x就变成了9,它表示1314520的二进制中有9个1。
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);
为了便于解说,我们下面仅说明这个程序是如何对一个8位整数进行处理的。我们拿数字211来开刀。211的二进制为11010011。
+—+—+—+—+—+—+—+—+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | <—原数
+—+—+—+—+—+—+—+—+
| 1 0 | 0 1 | 0 0 | 1 0 | <—第一次运算后
+——-+——-+——-+——-+
| 0 0 1 1 | 0 0 1 0 | <—第二次运算后
+—————+—————+
| 0 0 0 0 0 1 0 1 | <—第三次运算后,得数为5
+——————————-+
整个程序是一个分治的思想。第一次我们把每相邻的两位加起来,得到每两位里1的个数,比如前两位10就表示原数的前两位有2个1。第二次我们继续两两相加,10+01=11,00+10=10,得到的结果是00110010,它表示原数前4位有3个1,末4位有2个1。最后一次我们把0011和0010加起来,得到的就是整个二进制中1的个数。程序中巧妙地使用取位和右移,比如第二行中$33333333的二进制为00110011001100….,用它和x做and运算就相当于以2为单位间隔取数。shr的作用就是让加法运算的相同数位对齐。
网友评论