位运算

二进制#

javaScriptnumber类型是双精确度(64位)。位运算是32位运算。

0.1 + 0.2 !== 0.3#

原因#

因为JavaScript是遵守 IEEE 754 标准,数字的范围在:-(2^53 -1) 2^53 -1,是有穷尽的,所以在0.1 + 0.2转换为二进制之后,会失去原有的精确度。

解决方法#

  1. 乘一个较大的数,再除这个数
((0.1 * 1000) + (0.2 * 1000)) / 1000 === 0.3 // true
  1. 使用es6中的变量,Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的。因此,Number.EPSILON的实质是一个可以接受的最小误差范围。
if(!Number.EPSILON) {
// Math.pow(2, -52) === Number.EPSILON
Number.EPSILON = Math.pow(2, -52)
}
function equal(a, b) {
// 如果当前精读比 最小精读还小,就说明两者相等
return Math.abs(a - b) < Number.EPSILON
}
equal(0.1 + 0.2, 0.3) // true

32位#

将10进制的数字,转换为2进制,且表现形式为32位

const print32 = (num) => {
let sum = 0
for (let i = 31; i >= 0; i--) {
// 利用位运算符 左移,然后 & 看是取 0 还是 取 1
sum += (num & (1 << i)) == 0 ? '0' : '1'
}
return sum.slice(1)
}
console.log(print32(1)) // 00000000000000000000000000000001

32为的最小值到最大值:-2^31 ~ 2^31 - 1

十进制转二进制#

let num = 4
num.toString(2) // '100'

二进制转十进制#

parseInt('100', 2) // 4

位运算符#

<< 左移#

00000000000000000000000000000001
1<<1 // 1 左移一位,变成 2,是1的2倍,因为2进制是每前进一位都是2的倍数
00000000000000000000000000000010

>> 有符号右移#

带符号右移,移动后补位的是符号位:正数补0,负数补1

-Math.pow(2,31) // 32位最小的数
10000000000000000000000000000000
-Math.pow(2,31) >> 1
11000000000000000000000000000000

>>> 无符号右移#

无符号右移,移动后补位的是0

-Math.pow(2,31) // 32位最小的数
10000000000000000000000000000000
-Math.pow(2,31) >> 1
01000000000000000000000000000000

~ 取反#

0变1

-1
11111111111111111111111111111111
~(-1) 所有1取反为0
00000000000000000000000000000000

|#

只要有一个是1就为1

&#

两个都是1才是1

^ 异或#

相同为0,不同为1

常见套路#

2倍#

1<<1 === 2 // => true
// 左移一位是当前数的 2 倍
n<<1 = 2n

取相反数#

-1 === ~1 + 1 // => true
1 === ~(-1) + 1 // => true
// 得出结论
-n = ~n + 1
n = ~(-n) + 1
-1的32位表示为
11111111111111111111111111111111
~(-1) 所有1取反为0
00000000000000000000000000000000
~(-1) + 1
00000000000000000000000000000001

不用多余变量交换#

let a = 1, b = 2;
a = a ^ b;
b = a ^ b;
a = a ^ b;
console.log(a, b); // => 2 1
00000000000000000000000000000001 // => 1 a
00000000000000000000000000000010 // => 2 b
// 异或后
00000000000000000000000000000011 // => 3 a
00000000000000000000000000000010 // => 2 b
// 异或后
00000000000000000000000000000001 // => 1 b
00000000000000000000000000000011 // => 3 a
// 异或后
00000000000000000000000000000010 // => 2 a
// 此时 b = 1, a = 2 交换完成
a = a ^ b
b = a ^ b ^ b = a
a = a ^ b ^ a = b
tip

可以得出下面的结论:

  • a ^ 0 = a
  • a ^ a = 0
  • a ^ b ^ a = b ^ (a ^ a) = b ^ 0 = b