JavaScript中的&,|,~,~~,>>,<<,>>>,<<<等运算符和运算的源码 、补码 、反码的关系

318 阅读11分钟

PS: 一直以来都是把学习的笔记放在本地的文档上,缺少整理,现在就把一些实际开发中遇到的问题抽取一些记录在这里,希望能给工作和学习上的小伙伴们提供一点帮助

1. 首先我们来了解一下js中的位运算中的原码、补码、反码等相关知识:

A、原码

一个数在计算机中是以二进制的形式存在的,其中第一位存放符号, 正数为 0, 负数为 1。原码就是用第一位存放符号的二进制数值。 例如 2 的原码为 00000010,-2 的原码为10000010。

————————————————

B、反码

正数的反码是其本身。负数的反码是在其原码的基础上,符号位不变,其余各位取反,即 0变 1,1 变 0。

[+3]=[00000011]原=[00000011]反

[-3]=[10000011]原=[11111100]反

可见如果一个反码表示的是负数,并不能直观的看出它的数值,通常要将其转换成原码再计算。

————————————————

C、补码

正数的补码是其本身。负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(即负数的补码为在其反码的基础上+1)。

[+3]=[00000011]原=[00000011]反=[00000011]补

[-3]=[10000011]原=[11111100]反=[11111101]补

可见对于负数,补码的表示方式也是让人无法直观看出其数值的,通常也需要转换成原码再计算。

————————————————

D、进制数的转换(这里以10进制和2进制为主,另外还有8和16进制的,有兴趣可以自行查找资料)

二进制与十进制的转换 二进制与十进制的区别在于数运算时是逢几进一位。二进制是逢 2 进一位,十进制也就是我们常用的 0-9 是逢 10 进一位

正整数的十进制转二进制

正整数的十进制转二进制的方法为将一个十进制数除以 2,得到的商再除以 2,以此类推直到商等于 1 或 0 时为止,倒取除得的余数,即为转换所得的二进制数的结果。 例如把 52 换算成二进制数,计算过程如下:

52 除以 2 得到的余数依次为:0、0、1、0、1、1,倒序排列,所以 52 对应的二进制数就是110100。(从最后一位余数开始哦)

————————————————

负整数的十进制转二进制

负整数的十进制转二进制为将该负整数对应的正整数先转换成二进制,然后对其“取反”,再对取反后的结果+1。即负整数采用其二进制补码的形式存储。 例如 -52 的原码为 10110100,其反码为 11001011,其补码为 11001100。所以 -52 转换为二进制后为 11001100。 (正整数的原码、反码、补码是一样的)

————————————————

十进制小数转二进制

十进制小数转二进制的方法为“乘 2 取整”,对十进制小数乘 2 得到的整数部分和小数部分,整数部分即是相应的二进制数码,再用 2 乘小数部分(之前乘后得到新的小数部分),又得到整数和小数部分如此不断重复,直到小数部分为 0 或达到精度要求为止。第一次所得到为最高位,最后一次得到为最低位。

如下:

0.25的二进制

0.25*2 = 0.5 取整是0

0.5*2 = 1.0 取整是1

即0.25的二进制数是0.01(第一次所得到的为最高位,最后一次得到的为最低位)

0.5125的二进制

0.8125*2 = 1.625 取整是1

0.625*2 = 1.25 取整是1

0.25*2 = 0.5 取整是0

0.5*2 = 1.0 取整是1

即8.125的二进制数是0.1101(第一次所得到的为最高位,最后一次得到的为最低位)

————————————————

二进制转十进制

从最后一位开始算,依次列为第 0、1、2…位,第 n 位的数(0 或 1)乘以 2 的 n 次方,将得到的结果相加就是得到的十进制数。

例如二进制为 110 的数,将其转为十进制的过程如下

个位数 0 与 2º 相乘:0 × 2º = 0

十位数 1 与 2¹ 相乘:1 × 2¹ = 2

百位数 1 与 2² 相乘:1 × 2² = 4

将得到的结果相加:0+2+4=6 所以二进制 110 转换为十进制后的数值为 6。小数二进制用数值乘以 2 的负幂次然后相加

————————————————

2、JavaScript 中的位运算

在 ECMAScript 中按位操作符会将其操作数转成补码形式的有符号 32 位整数。JavaScript 中的位运算有:&(按位与)、|(按位或)、~(取反)、^(按位异或)、<<(左移)、>>(有符号右移)和>>>(无符号右移)。

A、&按位与

对每一个比特位执行与(AND)操作。只有 a 和 b 都是 1 时,a & b 才是 1。例如:9(base10) & 14(base 10) = 1001(base2) & 1110(base 2) = 1000(base 2) = 8(base 10)因为当只有 a 和 b 都是 1 时,a&b 才等于 1,所以任一数值 x 与 0(二进制的每一位都是 0)按位与操作,其结果都为 0。将任一数值 x 与 -1(二进制的每一位都是 1)按位与操作,其结果都为 x。利用 & 运算的特点,我们可以用以简单的判断奇偶数

公式:(n & 1) === 0 //true 为偶数,false 为奇数。

1&1=1 > 0001&0001=0001 奇数

2&1=0 -> 0010&0001=0000 偶数

因为 1 的二进制(0001)只有最后一位为 1,其余位都是 0,所以其判断奇偶的实质是判断二进制数最后一位是 0 还是 1。奇数的二进制最后一位是 1,偶数是 0。

当然还可以利用 JS 在做位运算时会舍弃掉小数部分的特性来做向下取整的运算,因为当 x为整数时有 x&-1=x,所以当 x 为小数时有 x&-1===Math.floor(x)。

-1的补码: 0001(原码)--》1110(反码)--》1110+1 = 1111 (反码+1得到补码)

0001 & 1111 = 0001 (1&1=1,1&0=0,0&1=0)

1&-1=1

2&-1=2

2.5&-1=2

————————————————

B、|按位或

对每一个比特位执行或(OR)操作。如果 a 或 b 为 1,则 a | b 结果为 1。 例如: 9(base10) | 14(base 10) = 1001(base2) | 1110(base 2) = 1111(base 2) = 15(base 10)

因为只要 a 或 b 其中一个是 1 时,a|b 就等于 1,所以任一数值 x 与-1(二进制的每一位都是 1)按位与操作,其结果都为-1。将任一数值 x 与 0(二进制的每一位都是 0)按位与操作,其结果都为 x。同样,按位或也可以做向下取整运算,因为当 x 为整数时有 x|0=x,所以当 x 为小数时有x|0===Math.floor(x)。

-1的补码是1111,所以x|-1肯定是-1,1111--》1111-1=1110(反码)--》1000 ... 0001 --> -1

————————————————

C、^按位异或

对每一对比特位执行异或(XOR)操作。当 a 和 b 不相同时,a ^ b 的结果为 1,向相同时为0(1^1=0,0^0=0,1^0=1,0^1=1)。

例如: 9(base 10) ^ 14(base 10) = 1001(base2) ^ 1110(base 2) = 0111(base 2) = 7(base 10)

将任一数值 x 与 0 进行异或操作,其结果为 x。将任一数值 x 与 -1 进行异或操作,其结果为 ~x,即 x^-1=~x。

2^-1=-3 --->- 0010 ^ 0001 =- 0011 = -3

~2 -》 0010 (原码)-》 1101(反码,取反后就是对应负数的补码,按照补码求原码的方法来求原码即可) -》 1101-1 = 1100 --》 0011 --》 -3

同样,按位异或也可以做向下取整运算,因为当 x 为整数时有 (x^0)===x,所以当 x 为小数时有 (x^0)===Math.floor(x)。

————————————————

D、~取反

对每一个比特位执行非(NOT)操作。~a 结果为 a 的反转(即反码)

9 (base 10) = 00000000000000000000000000001001 (base 2)

~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)

0110 - 1 = 0101(反码) ==》1000 。。。 1010 (原码) --》- (2^3 + 2^1) = -(8+2)=-10

将~得到的反码当成某个负数的补码,然后,补码-1得到反码,反码取反得到原码,即可得到其对应的负数

负数的二进制转化为十进制的规则是,符号位不变,其他位取反后加 1。

对任一数值 x 进行按位非操作的结果为 -(x + 1)。~~x=x

同样,取反也可以做向下取整运算,因为当 x 为整数时有 ~~x===x,

所以当 x 为小数时有~~x===Math.floor(x)。

————————————————

E、<<左移运算

它把数字中的所有数位向左移动指定的数量,向左被移出的位被丢弃,右侧用 0 补充。例如,把数字 2(等于二进制中的 0010)左移 5 位,结果为 64(等于二进制中的 1000000(右侧多了5个0))

因为二进制 10 转换成十进制的过程为 1×2¹+0×2º,在运算中 2 的指数与位置数相对应,当左移五位后就变成了 1×2¹⁺ ⁵+0×2º⁺ ⁵= 1×2¹×2⁵+0×2º×2⁵ = (1×2¹+0×2º)×2⁵。所以由此可以看出当 2 左移五位就变成了 2×2⁵=64。所以有一个数左移 n 为,即为这个数乘以 2 的 n 次方。x<<n === x*2ⁿ 。

同样,左移运算也可以做向下取整运算,因为当 x 为整数时有 (x<<0)===x,所以当 x 为小数时有 (x<<0)===Math.floor(x)

5-->0101 ---> 0001 0100 --> 2^2+2^4=4+16=20
console.log(5 << 2);  //返回值20

————————————————

F、有符号右移运算>>

它把 32 位数字中的所有数位整体右移,同时保留该数的符号(正号或负号)。有符号右移运算符恰好与左移运算相反。例如,把 64 右移 5 位,将变为 2。因为有符号右移运算符与左移运算相反,所以有一个数左移 n 为,即为这个数除以 2 的 n次方。x<<n === x/2ⁿ 。同样,有符号右移运算也可以做向下取整运算,因为当 x 为整数时有 (x>>0)===x,所以当x 为小数时有 (x>>0)===Math.floor(x)

2<<5 = 64

64>>5 = 2

console.log(1000 >> 8);  //返回值3



1000的原码是 0000 0000 0000 0000 0000 0011 1110 1000
1000 >> 8 = 0000 0000 0000 0000 0000 0000 0000 0011 = 2^1+2^0 = 3


console.log(-1000 >> 8); //返回值 -4

-1000的补码求解过程:

1000 0000 0000 0000 0000 0011 1110 1000  
--》符号位变化  --》 0变成1

1111 1111 1111 1111 1111  1100 0001 0111 
---》 除了符号位,其他取反,得到反码

1111 1111 1111 1111 1111  1100 0001 0111 + 1 
= 1111 1111 1111 1111 1111  1100 0001 1000 
---》反码+1得到补码,左移右移是操作补码的


-1000>>8

-->1111 1111 1111 1111 1111 1111 1111 1100  
(右移失去的位使用符号位1来填补,这就是-1000>>8代表的是补码反向求
到的二进制原码的十进制数,ps:后面的0001 1000不见了)

1111 1111 1111 1111 1111 1111 1111 1100 - 1 
= 1111 1111 1111 1111 1111 1111 1111 1011 --》得到反码

1000 0000 0000 0000 0000 0000 0000 0100 
---》 -2^2 = -4 
---> -1000>>8 = -4

————————————————

G、无符号右移运算>>>

它将无符号 32 位数的所有数位整体右移。对于正数,无符号右移运算的结果与有符号右移运算一样,而负数则被作为正数来处理。

-9 (base 10): 11111111111111111111111111110111 (base 2)

-9 >>> 2 (base 10): 00111111111111111111111111111101 (base 2) =

1073741821 (base10)

根据无符号右移的正数右移与有符号右移运算一样,而负数的无符号右移一定为非负的特征,可以用来判断数字的正负,如下:

function isPos(n) {
  return (n === (n >>> 0)) ? true : false;
}  
isPos(-1); // false
isPos(1); // true

console.log(1000 >> 8);  //返回值3
console.log(1000 >>> 8);  //返回值3


console.log(-1000 >> 8);  //返回值 -4
console.log(-1000 >>> 8);  //返回值 16777212

image.png

0000 0000 1111 1111 1111 1111 1111 1100 -->一步到位,变成正数后,直接取值即可-》 16777212

————————————————