写在前面的
在一般项目中很少会用到位运算,这次是在开发图片滑动验证时需要用到位运算获取rgb的三个色值。虽然大学时候《计算机组成原理》课讲过,但是现在全还给老师了。
顺便吐槽一下,普遍大学老师讲课都不会说这个课程对于该专业在未来会用到什么地方、什么领域,只是一味的告诉学生“期末要考”,导致很大一批人都是学生思想,只想着死背,期末过了就行。
而我就是这样的,当时学完原码补码反码这一节后,就再也没去上课了,想着反正临期末的时候背一下就行。现在想想很后悔,《数据结构》、《WEB网络》、《线性代数》、《计算机组成原理》这些在工作中都会用到的课程,我全都是飘过,好气。
不过为时不晚,现在补也来得及,下面重新学习并整理了一下位运算相关知识:
什么是位运算?
众所周知,程序中的代码在计算机内存中都是以二进制(补码)的形式存储,位运算就是直接对二进制位进行计算,位运算符可以直接处理每一个比特位(bit),所以是非常底层的运算,速度极快,但是不直观,一眼看不懂想要实现什么结果。
位运算符概览
| # | 符号 | 名称 | 含义 |
|---|---|---|---|
| 1 | & | 按位与 | 若两个二进制位都为1,则该位的结果值为1,否则为0 |
| 2 | | | 按位或 | 两个二进制位中只要有一个为1,该位的结果值为1 |
| 3 | ^ | 按位异或 | 若两个二进制位不相同,则返回1,否则返回0 |
| 4 | ~ | 取反 | 一元运算符,对一个二进制数按位取反,将0变1,将1变0 |
| 5 | << | 左移 | 将一个数的各二进制位全部左移N位,右补0 |
| 6 | >> | 右移 | 将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0 |
| 7 | >>> | 带符号位右移 | 同上,只是符号位也要跟着移动 |
位运算符详解
1、&(按位与)
逐位比较两个二进制,若两个二进制位都为1,则该位的结果值为1,否则为0; 例子:2 & 3
2的二进制数为:0010,3的二进制数为:0011,下文将不再解释
| 二进制 | 分解 | 第4位 | 第3位 | 第2位 | 第1位 |
|---|---|---|---|---|---|
| 0010 | => | 0 | 0 | 1 | 0 |
| 0011 | => | 0 | 0 | 1 | 1 |
| 结果 | 0 | 0 | 1 | 0 | |
只有第2位相同都是1,所以 2 & 3 结果为0010,也就是2 | |||||
| 可以在浏览器控制台测试: |
console.log("2&3运算的结果是: " + (2&3));
// 2&3运算的结果是: 22
2、|(按位或)
逐位比较两个二进制,只要其中一个位的值是1,就返回1,否则为0 例子:2 | 3
| 二进制 | 分解 | 第4位 | 第3位 | 第2位 | 第1位 |
|---|---|---|---|---|---|
| 0010 | => | 0 | 0 | 1 | 0 |
| 0011 | => | 0 | 0 | 1 | 1 |
| 结果 | 0 | 0 | 1 | 1 |
2 | 3 结果为0011,也就是3
console.log("2|3运算的结果是: " + (2|3));
// 2|3运算的结果是: 32
3、^(按位异或)
逐位比较两个二进制,若两个二进制位不相同,则返回1,否则返回0 例子:2 ^ 3
| 二进制 | 分解 | 第4位 | 第3位 | 第2位 | 第1位 |
|---|---|---|---|---|---|
| 0010 | => | 0 | 0 | 1 | 0 |
| 0011 | => | 0 | 0 | 1 | 1 |
| 结果 | 0 | 0 | 0 | 1 |
2 ^ 3 结果为0001,也就是1
console.log("2^3运算的结果是: " + (2^3));
// 2^3运算的结果是: 12
4、~(按位取反)
返回每个二进制位的相反值,1变0,0变1 例子:~ 3
| 二进制 | 分解 | 第4位 | 第3位 | 第2位 | 第1位 |
|---|---|---|---|---|---|
| 0010 | => | 0 | 0 | 1 | 1 |
| 取反 | 1 | 1 | 0 | 0 | |
这里取反后得到结果为:1100,最高位是1代表这个数为负数,负数转原码规则是: | |||||
| 保留最高位(符号位),其余先取反,再加1,于是: | |||||
| 二进制 | 分解 | 第4位 | 第3位 | 第2位 | 第1位 |
| - | - | - | - | - | - |
| 1100 | => | 1 | 1 | 0 | 0 |
| 取反 | 0 | 0 | 1 | 1 | |
| 加1 | 0 | 1 | 0 | 0 | |
所以最终结果是:负数的0100,也就是-4 |
~ 3结果为1100
console.log("~3运算的结果是: " + (~3));
// ~3运算的结果是: -42
5、<<(左移)
将一个数的二进制值向左移动指定的位数,尾部补0 例子:3 << 1
| 二进制 | 分解 | 第5位 | 第4位 | 第3位 | 第2位 | 第1位 |
|---|---|---|---|---|---|---|
| 0011 | => | 0 | 0 | 0 | 1 | 1 |
| 向左移1位 | 0 | 0 | 1 | 1 | 0 |
3 << 1 结果为0110,也就是6
console.log("3 << 1运算的结果是: " + (3<<1));
// 3 << 1运算的结果是: 62
6、>>(右移)
将一个数的二进制值向右移动指定的位数,头部补0,最高位(符号位)不参与移动 例子:-4 >> 1
| 二进制 | 分解 | 第4位 | 第3位 | 第2位 | 第1位 |
|---|---|---|---|---|---|
| 1100 | => | 1 | 1 | 0 | 0 |
| 向右移1位 | 1 | 1 | 1 | 0 | |
1110最高是1,说明是负数,按照规则,先取反再加1得0010,再加上符号 | |||||
所以 -4 >> 1 结果为负的0010,也就是-2 |
console.log("-4 >> 1运算的结果是: " + (-4>>1));
// -4 >> 1运算的结果是: -22
7、>>>(右移)
将一个数的二进制值向右移动指定的位数,头部补0,最高位(符号位)要参与移动 例子1,对于正数:4 >>> 1
| 二进制 | 分解 | 第4位 | 第3位 | 第2位 | 第1位 |
|---|---|---|---|---|---|
| 0100 | => | 0 | 1 | 0 | 0 |
| 向右移1位 | 0 | 0 | 1 | 0 | |
4 >>> 1 结果为0010,也就是2 | |||||
4 >> 1 结果也为0010 |
console.log("4 >>> 1运算的结果是: " + (4>>>1));
// 4 >>> 1运算的结果是: -22
例子2,对于负数:-4 >>> 1 结果是:
console.log("-4 >>> 1运算的结果是: " + (-4>>>1));
// -4 >>> 1运算的结果是: 21474836462
为什么是这个结果?
在
JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。
| >>>运算 | |
|---|---|
| -4的二进制 | 11111111111111111111111111111100 |
| 带符号向右移1位 | 01111111111111111111111111111110 |
所以结果是01111111111111111111111111111110,也就是十进制的2147483646 |
本篇是位运算的基础介绍,下一章是位运算进阶,在算法中常用到的技巧。