网站导航:首页-WEB前端/后端-JavaScript中的位运算

JavaScript中的位运算

最近更新:2020-05-30

在JavaScript中,运算符分为算数运算符、比较运算符、逻辑运算符、赋值运算符和位运算符等。这其中,许多人对位运算了解的比较少,本文将主要介绍JS中的位运算。为了了解位运算,我们首先介绍一些概念。

二进制数

平时生活中使用最多的就是十进制数,即每一位数都由 0-9 这十个数字中的一个组成,逢十进一。例如:数字365可以拆分成为如下形式:

数字365的十进制分解

通过上式可以看出,数字365中的每一位都具有不同的位权,比如数字3的位权是100,表示3个100,数字6的位权是10,表示6个10,数字5的位权是1,表示5个1,而每一位的位权,都是10的n次幂,这里将10称为基数。

而二进制数,就是以2为基数的一种数制,每一位只有0和1两个数字组成,一个8位的二进制数如下表所示:

二进制的位权

那么,二进制数 10010 则表示:

二进制数10010的分解

所以二进制数 10010 表示十进制中的18。

二进制数的溢出

计算机中二进制数的存储通常有位数的限制,对于n位二进制数,只能表示2的n次幂个数字,比如3位二进制数,只能存储 000、001、010、011、100、101、110、111 对应就是 0-7 这8个数字,这种情况下运算 7 + 1 时,对应的二进制运算为 111 + 001 正常应该等于 1000 但由于是3位的二进制数,所以最高位的1无法存储,所以运算结果实际为 000。

原码、反码和补码

刚刚举例的是无符号二进制数,对于有符号的情况,通常将最高的一位作为符号位,0表示正数,1表示负数,这种给带符号的二进制编码方式称为原码。下表列出了带符号的4位二进制数原码表示的所有情况

带符号的4位二进制数的原码表示

但是这种二进制表示法存在很多问题,比如 1 + (-1) 对应的二进制数的运算为 0001 + 1001 = 1010 运算结果为 -2,这是由于符号位所引起的,要向排除这种错误,需要通过复杂的运算,虽然原码直观易懂,易于转换。但用来实现加减法的话,运算规则总归是太复杂。原码最大的问题就在于一个数加上他的相反数不等于零,所以直接用一个正数按位取反来表示负数,这种给带符号的二进制编码方式称为反码。正数的反码是正数本身,负数的反码是符号位保持不变,其他位按位取反。下表列出了带符号的4位二进制数反码表示的所有情况

带符号的4位二进制数的反码表示

利用反码表示的二进制运算就解决了刚刚的问题,1 + (-1) 对应的二进制数运算为 0001 + 1110 = 1111 运算结果为 -0,这个运算没什么问题,但是负数的加法还是存在问题的,比如 (-1) + (-2) 对应的二进制数运算为 1110 + 1101 = 1011(最高位溢出),运算结果为 -4,这个运算明显也是不正确的。但是,我们可以发现,运算出的值 1011 虽然在反码中表示 -4,但是在原码中表示 -3,恰好是运算的正确答案。为了解决这个问题,我们在反码的基础上,再加1,将这种带符号的二进制编码方式称为补码。但这只是补码的一种计算方式,并不是补码的定义,关于补码的定义,请参考《计算机组成原理》一书。正数的补码是正数本身,负数的补码是其反码加1。(还有一种计算方式:负数的补码为它的原码按照从低位到高位的顺序,第一个1及之前的0保持不变,其他位按位取反。下表列出了带符号的4位二进制数原码表示的所有情况

带符号的4位二进制数的补码表示

补码中的 -0 与 0 的编码相同,所以不再需要 1000 来表示 -0,于是就定义 1000 这个值为 -8。在看一下利用补码表示的二进制运算是否能正确运算上面两个式子,1 + (-1) 对应二进制数运算为 0001 + 1111 = 0000 运算结果为0,(-1) + (-2) 对应的二进制数运算为 1111 + 1110 = 1101 运算结果为 -3,这些运算都没什么问题,当前计算机中的二进制数都是以补码的形式存储的。

在JavaScript中,在输出二进制数字的时候,为了方便,会自动将负数以符号和二进制原码的形式来展示。

JavaScript中的位运算

位运算是在数字底层(即表示数字的每个二进制数位)进行操作的,在JavaScript中的位运算都会按照32位二进制数进行运算,对于非数字类型的,将转换为数字类型,NaN将按0处理,下面介绍与位运算相关的各种运算符。

按位非(NOT)运算

按位非运算用“~”表示,它是 ECMA Script 中为数不多的与二进制算术有关的运算符之一。非运算将数字的每个二进制位都进行取反运算得到新的数字。例如对数字13进行按位取反运算:

// 1、数字13转换为32位二进制数为 0000 0000 0000 0000 0000 0000 0000 1101
// 2、将32位二进制数按位取反,得到新的二进制数为 1111 1111 1111 1111 1111 1111 1111 0010
// 3、将运算得到的值再转换为正常的十进制数字为 -14

let num = 13;
console.log(~num); // -14

通常按位取反的结果相当于取相反数后再减1。

按位与(AND)运算

按位与运算用“&”表示,直接对两个数字的二进制形式进行运算。它把两个数字中对应的数位对齐,然后进行与运算,运算规则为 1 & 1 = 1 0 & 1 = 0 1 & 0 = 0 0 & 0 = 0。例如,对数字13和数字5进行按位与运算:

// 1、数字13转换为32位二进制数为 0000 0000 0000 0000 0000 0000 0000 1101
// 2、数字5转换为32位二进制数为 0000 0000 0000 0000 0000 0000 0000 0101
// 3、将两组32位二进制数进行按位与运算,得到新的二进制数为 0000 0000 0000 0000 0000 0000 0000 0101
// 4、将运算得到的值再转换为正常的十进制数字为 5

let num1 = 13, num2 = 5;
console.log(num1 & num2); // 5

从按位与运算的规则,可以看出,一个二进制位与1相与,可得到相同的二进制数字,与0相与,可得到0,所以按位与运算通常用来将一个数的某个二进制位置0。

按位或(OR)运算

按位或运算用“|”表示,也是直接对两个数字的二进制形式进行运算。它把两个数字中对应的数位对齐,然后进行或运算,运算规则为 1 | 1 = 1 0 | 1 = 1 1 | 0 = 1 0 | 0 = 0。例如,对数字13和数字5进行按位或运算:

// 1、数字13转换为32位二进制数为 0000 0000 0000 0000 0000 0000 0000 1101
// 2、数字5转换为32位二进制数为 0000 0000 0000 0000 0000 0000 0000 0101
// 3、将两组32位二进制数进行按位与运算,得到新的二进制数为 0000 0000 0000 0000 0000 0000 0000 1101
// 4、将运算得到的值再转换为正常的十进制数字为 13

let num1 = 13, num2 = 5;
console.log(num1 | num2); // 13

从按位或运算的规则,可以看出,一个二进制位与0相或,可得到相同的二进制数字,与1相或,可得到1,所以按位或运算通常用来将一个数的某个二进制位置1。

按位异或(XOR)运算

按位异或运算用“^”表示,也是直接对两个数字的二进制形式进行运算。它把两个数字中对应的数位对齐,然后进行或运算,运算规则为 1 ^ 1 = 0 0 ^ 1 = 1 1 ^ 0 = 1 0 ^ 0 = 0。例如,对数字13和数字5进行按位异或运算:

// 1、数字13转换为32位二进制数为 0000 0000 0000 0000 0000 0000 0000 1101
// 2、数字5转换为32位二进制数为 0000 0000 0000 0000 0000 0000 0000 0101
// 3、将两组32位二进制数进行按位与运算,得到新的二进制数为 0000 0000 0000 0000 0000 0000 0000 1000
// 4、将运算得到的值再转换为正常的十进制数字为 8

let num1 = 13, num2 = 5;
console.log(num1 ^ num2); // 8

从按位异或运算的规则,可以看出,一个二进制位与0相异或,可得到相同的二进制数字,与1相异或,可得到取反的结果,所以按位异或运算通常用来将一个数的某个二进制位取反。

左移运算

左移运算用“<<”表示,它把数字中除了符号位的所有数位向左移动指定的数量,符号位保持不变,右侧空余的地方用0填充。例如,对数字-3进行左移2位运算:

// 1、数字-3转换为32位二进制数为 1111 1111 1111 1111 1111 1111 1111 1101
// 2、将32位二进制数进行左移2位运算,得到新的二进制数为 1111 1111 1111 1111 1111 1111 1111 0100
// 3、将运算得到的值再转换为正常的十进制数字为 -12

let num = -3;
console.log(num << 2); // -12

有符号右移运算

有符号右移运算用“>>”表示,它把数字中除了符号位的所有数位向右移动指定的数量,符号位保持不变,左侧空余的地方用符号位填充。例如,对数字-60进行带符号右移4位运算:

// 1、数字-60转换为32位二进制数为 1111 1111 1111 1111 1111 1111 1100 0100
// 2、将32位二进制数进行带符号右移4位运算,得到新的二进制数为 1111 1111 1111 1111 1111 1111 1111 1100
// 3、将运算得到的值再转换为正常的十进制数字为 -4

let num = -60;
console.log(num >> 4); // -4

无符号右移运算

无符号右移运算用“>>>”表示,它把数字中包含符号位的所有数位向右移动指定的数量,左侧空余的地方用0填充。得到的结果是无符号的32位二进制数字,例如,对数字-60进行无符号右移2位运算和对数字-10进行无符号右移0位运算:

// 1、数字-60转换为32位二进制数为 1111 1111 1111 1111 1111 1111 1100 0100
// 2、将32位二进制数进行无符号右移2位运算,得到新的二进制数为 0011 1111 1111 1111 1111 1111 1111 0001
// 3、将运算得到的值再转换为正常的十进制数字为 1073741809

let num1 = -60;
console.log(num1 >>> 2); // 1073741809

// 1、数字-10转换为32位二进制数为 1111 1111 1111 1111 1111 1111 1111 0110
// 2、将32位二进制数进行无符号右移0位运算,得到新的二进制数与原来的相同(但新的二进制数为32位无符号的二进制数)
// 3、将运算得到的值再转换为正常的十进制数字为 4294967286

let num2 = -10;
console.log(num2 >>> 0); // 4294967286

(正文完)

如果您对本文由任何意见或建议,或者想留言或评论,请随时提交工单进行反馈。

到底线啦,请:返回目录页|返回首页