位运算介绍

示例练习>>>

数据在计算机中都是以二进制形式储存的。比如在 int 类型中,最高位是符号位, 1 表示负数, 0 表示非负数。

正整数 负整数
源码 二进制中最高位是 0 二进制中最高位是 1
反码 和源码相同 最高位不变,其他位按位取反
补码 和源码相同 反码 + 1

在计算机中整数都是以补码的方式进行存储的,正整数的源码,反码和补码都是一样的,负整数的源码,反码,补码都不一样。

10000000 00000000 00000000 00001101 (-13的源码)
11111111 11111111 11111111 11110010 (-13的反码,除了最高位不变,其他位都取反)
11111111 11111111 11111111 11110011 (-13的补码,反码加 1 )

位运算的常见操作符

常见的位运算操作符有下面几种(注意,本书主要以 java 语言来写的,有些操作符在其他语言中不一定有)。

序号 运算符 说明 使用
1 & 按位与 只要有一个是 0,结果就为 0 。
2 | 按位或 只要有一个是 1 ,结果就为 1 。
3 ~ 按位非 如果是 0 就变成 1 ,如果是 1 就变成 0 。
4 ^ 按位异或 相同的为 0 ,不同的为 1 。
5 << 左移运算符 往左移一位,相当于放大一倍(特殊情况除外)。
6 >> 右移运算符 往右移一步,相当于缩小一倍(特殊情况除外)。
7 >>> 无符号右移运算符(java) 无符号右移,高位补 0 。

1,& 按位与 – 只要有一个是 0 ,结果就是 0 。

0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1 (都是 1 的时候,结果才为 1 )

2,| 按位或 – 只要有一个是 1 ,结果就是 1 。

0 & 0 = 0(都是 0 的时候,结果才为 0 )
0 & 1 = 1
1 & 0 = 1
1 & 1 = 1 

3,~ 按位非 – 如果是 0 就变成 1 ,如果是 1 就变成 0 。

~1 = 0 
~0 = 1

4,^ 按位异或 – 相同的为 0 ,不同的为 1 。

1 ^ 1 = 0 
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0    

5,<< 左移运算符 – 往左移一位,相当于放大一倍。

1 << 1 结果为 2
2 << 1 结果为 4
2 << 3 结果为 16

移动 1 位相当于乘以 2 ,移动 2 位相当于乘以 4 ,移动 3 位相当于乘以 8,…… 。特殊情况,比如出现溢出,又比如 0 无论怎么移还是 0 。

6,>> 右移运算符 – 往右移一位,相当于缩小一倍。

32 >> 1 结果为 16
32 >> 2 结果为 8
32 << 3 结果为 4

注意:右移的时候高位如果是 0 ,移动的时候补 0 ,高位如果是 1 ,移动的时候补 1 。

00000000 00000000 00000000 00100000 (是十进制的32)
00000000 00000000 00000000 00001000 (右移两位变成8,最高位补0)

11111111 11111111 11111111 11100000 (是十进制的-32)
11111111 11111111 11111111 11111000 (右移两位变成-8,最高位补1) 

7,>>> 无符号右移运算符 – 无符号右移,高位一律补 0。

00000000 00000000 00000000 00100000 (是十进制的 32 )
00000000 00000000 00000000 00001000 (右移两位变成 8 ,最高位是补 0 )

11111111 11111111 11111111 11100000 (是十进制的 -32)
00111111 11111111 11111111 11111000 (右移两位变成 1073741816,最高位是补 0 ) 

位运算的一些简单操作

1,判断 x 是奇数还是偶数。

x & 1

(x & 1) == 0 是偶数
(x & 1) == 1 是奇数   

2,x 乘以 2 的 n 次方。

x << n

比如 x = 3,n = 5
00000000 00000000 00000000 00000011 (x = 3)
00000000 00000000 00000000 01100000 (x << 5),结果是 3*32 = 96

3, x 除以 2 的 n 次方。

x >> n

比如x = 31,n = 3    
00000000 00000000 00000000 00011111(x = 31)
00000000 00000000 00000000 00000011(x >> 3),结果是 31/8 = 3

这里要注意如果 x 是非负数,x >> 3 和 x / 8 结果一样。如果 x 是负数,x >> 3 是往下取值,x / 8 是往上取整。
11111111 11111111 11111111 11100001 (x = -31)    
11111111 11111111 11111111 11111100 (x >> 3),结果是 -4 。而 -31 / 8 = -3 。  

4,消去 x 二进制中最右边的 1 。

x & (x-1)

00000000 00000000 00000000 00001101 (x = 13)
00000000 00000000 00000000 00001100 (x-1 = 12)
00000000 00000000 00000000 00001100  运算之后把 x 最右边的 1 给消掉了(结果)

11111111 11111111 11111111 11110011 (x = -13)
11111111 11111111 11111111 11110010 (x-1 = -14)
11111111 11111111 11111111 11110010  运算之后把 x 最右边的 1 给消掉了。(结果)

5,求 x 的相反数。

除了在前面加个符号以外,我们还可以这样写:~(x-1)或者~x+1
比如 x 等于 10 ,计算结果为 -10 。如果 x=-10 ,计算结果为 10 。

00000000 00000000 00000000 00001010 (x = 10)   
00000000 00000000 00000000 00001001 (x = 10-1=9)  
11111111 11111111 11111111 11110110 (x = ~(10-1)=-10)  
或者    
00000000 00000000 00000000 00001010 (x = 10)     
11111111 11111111 11111111 11110101 (x = ~10)   
11111111 11111111 11111111 11110110 (x = ~10+1=-10)         

6, x 的非运算。

~x = -x-1

00000000 00000000 00000000 00000110 (x = 6)
11111111 11111111 11111111 11111001 (~x = -7)

11111111 11111111 11111111 11111010 (x = -6)    
00000000 00000000 00000000 00000101 (~x = 5) 

7, x 的二进制位中从右边数第 n 位变成 1 ( n 从 1 开始)。

x|(1<<(n-1))

比如x = 13,n = 7
00000000 00000000 00000000 00001101 (x = 13)
00000000 00000000 00000000 01000000 (1<<(n-1))
00000000 00000000 00000000 01001101  右边数第7位变成了1(结果)

8, x 的二进制位中从右边数第 n 位变成 0 ( n 从 1 开始)。

x&(~(1<<(n-1)))

比如x = 13,n = 3
00000000 00000000 00000000 00001101 (x = 13)
11111111 11111111 11111111 11111011 (~(1<<(n-1)))
00000000 00000000 00000000 00001001  右边数第3位变成了0(结果)

9,截取 x 二进制中最后 n 位的值。

x&((1<<n)-1)

比如x = 179,n = 5
00000000 00000000 00000000 10110011 (x = 179)
00000000 00000000 00000000 00011111 ((1<<n)-1)
00000000 00000000 00000000 00010011  截取二进制中的后面5位(结果)

10,只保留 x 二进制中右边第一个 1 ,其他的全部置为 0。

x & (-x)或者x & ~(x - 1)

比如x = 12
00000000 00000000 00000000 00001100 (x = 12)
11111111 11111111 11111111 11110100 (-x = -12)
00000000 00000000 00000000 00000100  只保留x二进制中右边的第一个1(结果)  

11,判断 x 的二进制中从右边数第 n 位是 0 还是 1 。

(x & (1 << (n - 1)))
(x & (1 << (n - 1))) == 0 第 n 位是 0 
(x & (1 << (n - 1))) != 0 第 n 位是 1 

12,不使用 if 语句求 x 的绝对值。

(x ^ (x >> 31)) - (x >> 31)或者(x + (x >> 31)) ^ (x >> 31)
如果是 x 是非负数,直接返回 x 的值。如果 x 是负数,则返回他的绝对值。

00000000 00000000 00000000 00001010(x = 10)     
00000000 00000000 00000000 00000000(x >> 31)     
00000000 00000000 00000000 00001010(x ^ (x >> 31))   
00000000 00000000 00000000 00001010 (x ^ (x >> 31)) - (x >> 31),结果是 10    

11111111 11111111 11111111 11110110(x = -10)     
11111111 11111111 11111111 11111111(x >> 31)     
00000000 00000000 00000000 00001001(x ^ (x >> 31))   
00000000 00000000 00000000 00001010 (x ^ (x >> 31)) - (x >> 31)  结果是 10       

13,返回 x 的符号(正数返回 1 ,负数返回 -1 , 0 返回 0 )。

(x >> 31) | (-x >>> 31)
如果x = 12,则返回1,如果x = -12则返回-1。

00000000 00000000 00000000 00001100(x = 12)  
00000000 00000000 00000000 00000000(x >> 31) 
11111111 11111111 11111111 11110100(-x = -12)
00000000 00000000 00000000 00000001(-x >>> 31)
00000000 00000000 00000000 00000001(x >> 31)|(-x >>> 31),结果是 1 。 

11111111 11111111 11111111 11110100(x = -12) 
11111111 11111111 11111111 11111111(x >> 31) 
00000000 00000000 00000000 00001100(-x = 12)   
00000000 00000000 00000000 00000000(-x >>> 31)
11111111 11111111 11111111 11111111(x >> 31)|(-x >>> 31),结果是 -1 。    

14,x 的二进制位从右边数第 n 位取反。

x ^ (1 << (n-1))

比如 x = 11,n = 4,或者 n = 5
00000000 00000000 00000000 00001011 (x = 11)
00000000 00000000 00000000 00000011 (右边第 4 位取反)
00000000 00000000 00000000 00011011 (右边第 5 位取反)

15,把 x 的二进制位右边 n 位全部变成 1 ,其他位不变。

x | ((1 << n)-1)

比如 x = 11, n = 10
00000000 00000000 00000000 00001011 (x = 11)
00000000 00000000 00000011 11111111 (右边 10 位全部变成 1 )

16,把 x 的二进制位右边 n 位全部变成 0 ,其他位不变。

x & (-1-((1<<n)-1))

比如 x = -7, n = 10 
11111111 11111111 11111111 11111001 (x = -7)
11111111 11111111 11111100 00000000 (右边 10 位全部变成 0 )
在二进制中 -1 实际上就是 11111111 11111111 11111111 11111111,全部为 1 。     

17, x 的二进制位右边 n 位取反,其他位不变。

x ^ ((1 << n)-1)

比如 x = 7, n = 10     
00000000 00000000 00000000 00000111 (x = 7)
00000000 00000000 00000011 11111000 (右边 10 位全部取反 )

18,x 的二进制位右边连续的 1 变成 0 ,其他位不变。

x & (x+1)

00000000 00000000 00000000 00110111 (x = 55)
00000000 00000000 00000000 00111000 (x+1 = 56)
00000000 00000000 00000000 00110000 (右边 3 个连续的 1 全部变成 0 )

19,x 的二进制位右边连续的 0 变成 1 ,其他位不变。

x | (x-1)

00000000 00000000 00000000 00101000 (x = 40)
00000000 00000000 00000000 00100111 (x-1 = 39)
00000000 00000000 00000000 00101111 (右边 3 个连续的 0 全部变成 1 )

20,x 的二进制位右边第一个 0 变成 1 ,其他位不变。

x | (x+1)

00000000 00000000 00000000 00100111 (x = 39)
00000000 00000000 00000000 00101000 (x+1 = 40)
00000000 00000000 00000000 00101111 (右边第一个 0 变成 1 )

21,取 x 的二进制位右边连续的 1 。

(x ^ (x+1)) >> 1

00000000 00000000 00000000 00100111 (x = 39)
00000000 00000000 00000000 00101000 (x+1 = 40)
00000000 00000000 00000000 00001111 (x ^ (x + 1))    
00000000 00000000 00000000 00000111 (取 x 的二进制位右边连续的 1 )

上面只是列出了位运算的一部分操作,实际上位运算的操作远不止这些。有些同学可能会说,简单的问题给搞复杂了,尤其第 5,12,13 等题,没必要搞那么麻烦。其实说的没错,在工作中代码尽量写的让大家都能看的懂。那这里为什么要列出来,第一可以帮助大家更好的理解二进制在计算机中的存储,第二可以更好的理解二进制的运算,第三可以更好的阅读官方源码。学过 java 的同学应该知道,上面第 10,13 题就是 Integer 类中的方法。你可以不这样写,但是你无法阻止官方这样写,我们写代码一方面给别人看,另一方面我们还要学会看懂别人的代码。

/**
 * Returns the signum function of the specified {@code int} value.  (The
 * return value is -1 if the specified value is negative; 0 if the
 * specified value is zero; and 1 if the specified value is positive.)
 *
 * @param i the value whose signum is to be computed
 * @return the signum function of the specified {@code int} value.
 * @since 1.5
 */
public static int signum(int i) {
    // HD, Section 2-7
    return (i >> 31) | (-i >>> 31);
}

信奥赛编程(刷题请进)>>>

经过两年的打磨,我的新作《算法秘籍》已经出版,有需要的可以点击购买。也可以点击 内容介绍 查看详情。