JavaScript中的位操做符

按位操做符

JavaScript中使用IEEE-754 64位存儲。位操做符不能直接操做64位的值,而是將它轉換爲二進制補碼形式的32位的整數,最後再將結果轉爲64位。32位中31位表示整數的值,第32位爲符號位(0爲正數,1爲負數)。每一位由二進制數存儲,31位中的每一位的索引表示2的次冪乘與每一位的0或者1。沒有使用到的位將使用0填充。javascript

舉一個例子🌰。31的32位二進制數表示爲0000 0000 0000 0000 0000 0000 0001 1111。第32位0表示符號位,11111爲有效位。java

1 1 1 1 1
2^4 * 1 2^3 * 1 2^2 * 1 2^1 * 1 2^0 * 1
16 8 4 2 1

負數

負數使用二進制存儲,可是使用二進制補碼表示數組

如何求一個數的二進制補碼?post

    1. 求出該數絕對值的二進制碼
    1. 將得到的二進制碼取反(1變成0,0變成1)
    1. 二進制碼加1

舉一個例子🌰。如何求出-31的二進制補碼。-31的二進制補碼的結果爲1111 1111 1111 1111 1111 1111 1110 0001。性能

  • -31的絕對值爲31,求出31的二進制的碼
0000 0000 0000 0000 0000 0000 0001 1111
複製代碼
  • 將31的二進制碼的0和1顛倒
1111 1111 1111 1111 1111 1111 1110 0000
複製代碼
  • 將二進制碼加1
1111 1111 1111 1111 1111 1111 1110 0000
                                     +1
---------------------------------------
1111 1111 1111 1111 1111 1111 1110 0001
複製代碼

無符號數

ECMAScript全部整數都是有符號位的。在無符號數中,第32位不表示符號,由於無符號數只能是正數,32位無符號數能夠表示更大的數。spa

特殊值的處理

在NaN和Infinity在位運算符中會被看成0處理。非數值類型會先使用Number()處理,而後再應用位操做符。設計

按位非(NOT)

按位非~會將數值的32位二進制的每一位取反(0變爲1,1變爲0)。按位非的操做符的本質取操做數的負值,而後減一3d

// -24
~23

// -101
~100

// 9-10

// 100-101
複製代碼

舉一個例子🌰。10進行按位非以後的結果,等於-11rest

0000 0000 0000 0000 0000 0000 0000 1010
// ~ NOT
1111 1111 1111 1111 1111 1111 1111 0101
複製代碼

按位與(AND)

按位與&, 本質上將兩個操做數的32位二進制數的每一位對齊。而後按以下的規則取值,1 & 1 等於 1; 1 & 0 等於 0;0 & 1 等於0;0 & 0等於0。code

舉一個例子🌰。10和5之間進行按位與操做的結果,等於0

0000 0000 0000 0000 0000 0000 0000 1010
// & AND
0000 0000 0000 0000 0000 0000 0000 0101
// 等於
0000 0000 0000 0000 0000 0000 0000 0000
複製代碼

按位或(OR)

按位與|, 本質上將兩個操做數的32位二進制數的每一位對齊。而後按以下的規則取值,1 | 1 等於 1; 1 | 0 等於 1;0 | 1 等於1;0 | 0等於0。

舉一個例子🌰。10和5之間進行按位或操做的結果,等於15

0000 0000 0000 0000 0000 0000 0000 1010
// & OR
0000 0000 0000 0000 0000 0000 0000 0101
// 等於
0000 0000 0000 0000 0000 0000 0000 1111
複製代碼

按位異或(XOR)

按位異或^, 本質上將兩個操做數的32位二進制數的每一位對齊。而後按以下的規則取值,1 ^ 1 等於 0; 1 ^ 0 等於 1;0 ^ 1 等於1;0 ^ 0等於0。

舉一個例子🌰。10和5之間進行按位異或操做的結果15。

0000 0000 0000 0000 0000 0000 0000 1010
// ^ XOR
0000 0000 0000 0000 0000 0000 0000 0101
// 等於
0000 0000 0000 0000 0000 0000 0000 1111
複製代碼

左移

左移(<<)將32位二進制向左移動指定的位數,空缺的位將會使用0填充。左移不會影響符號位

舉一個例子🌰。將5左移2位,等於20

0|000 0000 0000 0000 0000 0000 0000 0101
// <<2
0|000 0000 0000 0000 0000 0000 0001 0100
// 等於
(2^0 * 0) + (2^1 * 0) + (2^2 * 1) + (2^3 * 0) + (2^4 * 1) = 0 + 0 + 4 + 0 + 16 = 20
複製代碼

有符號位右移

右移(>>)將32位二進制向右移動指定的位數,可是保留符號位,右移空缺的符號位使用0填充

舉一個例子🌰。將31有符號右移3位,等於3。

0|000 0000 0000 0000 0000 0000 0001 1111
// >>3
0|000 0000 0000 0000 0000 0000 0000 0011
// 等於
(2^0 * 1) + (2^1 * 1) = 1 + 2 = 3
複製代碼

無符號位右移

無符號位右移,會將全部32位數都向右移動。對於正數來講右移和無符號位右移的結果是一致的。

舉一個例子🌰。將-31無符號右移28位。

1111 1111 1111 1111 1111 1111 1110 0001
// >>> 28
0000 0000 0000 0000 0000 0000 0000 1111
// 等於
(2^0 * 1) + (2^1 * 1) + (2^2 * 1) + (2^3 * 1) = 1 + 2 + 4 + 8 = 15
複製代碼

位操做符的奇妙用法

~~ 向下取整

按位非~。將操做數取負值,並減1。對於浮點數,會直接捨棄小數的部分。好比~11.1 === -12。而後將結果,再次執行按位非操做,就會獲得了去除小數位的原數字,~-12 === 11。所以咱們可使用~~代替Math.floor。

// 12
~~12.5

// 6
~~6.1

// 8
~~-8.1
複製代碼

Math.floor和~~性能對比

我分別使用~~和Math.floor,對包含了10000000個浮點數的數組,進行向下取整。~~耗時386毫秒,Math.floor耗時411毫秒.

~ 判斷索引是否存在

  • ~-1等於0(-1取正值後減1)

平時使用indexOf等API時,當查詢的字符串不存在時,indexOf會返回-1。可是Boolean(-1)等於true,因此咱們在使用時一般須要添加一個判斷'字符串'.indexOf('字') > -1,看上去並非那麼的簡潔。 -1按位非等於0,而Boolean(0)是等於false的。因此咱們能夠把不等式簡化成下面的樣子。

if ('米莉波比布朗'.indexOf('莉') > -1) {
}

// 等價於
if (~'米莉波比布朗'.indexOf('莉')) {
}
複製代碼

^ 不使用額外的空間交換兩個變量的值

在瞭解這個技巧以前須要先了解兩個準則:

  1. 兩個相同的數進行按位異或等於0
  2. 任意一個數與0進行按位異或等於自身
// 0
100 ^ 100
// 0
-99 ^ -99

// 100
100 ^ 0
// -2
0 ^ -2
複製代碼

在代碼中交換兩個變量的值,一般是經過第三個變量,或者使用ES6中的解構賦值

// 使用第三個變量
var a = 1
var b = 2
var c = a
a = b
b = c

// 使用解構賦值
var x = 1
var y = 2
[x, y] = [y, x]
複製代碼

使用異或按位運算符

let a = 2
let b = 3

a = a ^ b
// b = a ^ b ^ b => a ^ (b ^ b) => a ^ 0 = a
b = a ^ b
// a = a ^ b ^ a ^ b ^ b => (a ^ a) ^ (b ^ b) ^ b => 0 ^ 0 ^ b => 0 ^ b = b
a = a ^ b
複製代碼

^ 切換位

使用異或按位運算符,能夠用來切換二進制數中的某些位,這基於這樣一個事實:

  1. 任意一個數與0進行按位異或等於自身(上面提到過)
  2. 任意一個數與與1進行按位異或,老是被切換(參考下面的例子)
let a = 1
// 0
a = 1 ^ 1
// 1 (又變回了1)
a = a ^ 1
複製代碼

舉一個例子🌰,let a = 0b101011, 咱們想將中間的四位進行切換(0->1, 1->0)變爲0b110101。咱們建立一個掩碼,首尾爲0,中間四位爲1, 使用掩碼進行按位異或的操做。

// 掩碼
let mask = 0b011110
let a = 0b101011

// 0b110101
a ^ mask
複製代碼

& 關閉高位

使用按位與&, 能夠用來關閉二進制數中的某些位,這基於這樣一個事實:

  1. N & 0 === 0 任意一個數與0,按位與,等於0
  2. N & N === N 任意一個數與自身,按位與,等於自身
  3. N & -N === 0 任意一個數與自身負值,按位與,等於0
  4. N & -1 === N 任意一個數與-1,按位與,等於自身

好比咱們有一個8位的二進制數,咱們須要關閉後4位(設置爲0),咱們首先建立一個8位的位掩碼,位掩碼的後四位設置爲0,前四位設置爲1。接下來使用位掩碼與操做數進行按位與操做

const mask = 0b00001111

// 結果00001010,成功將後四位設置爲0
mask & 0b10101010
複製代碼

& 檢查設置位

可使用按位與,檢查二進制數中某一位是否設置了。

舉一個例子🌰,咱們須要檢查第五位是否有數字。

const mask = 0b10000

// false
0b100010 & mask === mask

// true
0b110010 & mask) === mask)

複製代碼

& 判斷奇數或偶數

奇數與偶數的二進制表示的特性:

  • 偶數的二進制的第一位始終是0
  • 奇數的二進制的第一位始終是1
// 0b0
0
// 0b1
1
// 0b10
2
// 0b11
3
// 0b100
4
// 0b101
5
// 0b110
6
複製代碼

因此咱們可使用0001做爲掩碼,使用0關閉高位,只保留第一位。當number & 1 === 1, number爲奇數。number & 1 === 0, number爲偶數。

  • 偶數與0b1,按位與,結果一定等於0
  • 奇數與0b1,按位與,結果一定等於1
// 奇數
(3 & 1) === 1

// 偶數
(4 & 1) === 0
複製代碼

<<,>> 移位實現乘除法

ps:😂在工程中,最好不要這樣寫,省得的被打。

左移1位,等於乘以2,左移2位,等於乘以4。可是對於乘以7或者乘以9該怎麼辦呢?乘以7,能夠分解爲左移3位並減去一位,a * 7 === (a << 3) - a。乘以9,能夠分解爲左移3位並加上一位a * 9 === (a << 3) + a

  • 左移1位,等價於,乘以2的1次方。
  • 左移2位,等價於,乘以2的2次方。
  • 左移3位,等價於,乘以2的3次方。
  • ...
a * 2 === a << 1
a * 3 === (a << 1) + a
a * 4 === a << 2
複製代碼
  • 右移1位,等價於,除以2的1次方。
  • 右移2位,等價於,除以2的2次方。
  • 右移3位,等價於,除以2的3次方。
  • ...
a / 2 === a >> 1
a / 4 === a >> 2
a / 8 === a >> 3
複製代碼

利用位操做符,解答leetcode

從這些題目中,能夠get到的點

  1. 2的n次冪,二進制中只有一個1。2的n次冪與2的n次冪減一,按位與,結果永遠是0。
  2. 可使用>>>右移代替除法
  3. 可使用二進制的0和1,模擬有或無
  4. 異或能夠用來實現簡單的去重
  5. 異或配合按位與能夠實現加法

參考

相關文章
相關標籤/搜索