数据在计算机中都是以二进制形式储存的。比如在 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);
}