起因
之前對(duì)js的一些涉及到二進(jìn)制的運(yùn)算符一直似懂非懂,看到了就一臉懵逼,還得去控制臺(tái)算一下。然后最近看算法的時(shí)候又看到了這個(gè)運(yùn)算符,這里就簡(jiǎn)單介紹一下學(xué)習(xí)這些位運(yùn)算符的過(guò)程。
注意:以下運(yùn)算均不涉及到小數(shù)。
過(guò)程
移位運(yùn)算符
<<
“<<”運(yùn)算符執(zhí)行無(wú)符號(hào)左移位運(yùn)算。在移位運(yùn)算過(guò)程中,符號(hào)位始終保持不變。如果右側(cè)空出位置,則自動(dòng)填充為 0;超出 32 位的值,則自動(dòng)丟棄。
先說(shuō)這句話是什么意思。左移位是二進(jìn)制的一種運(yùn)算,就是在不改變二進(jìn)制數(shù)值32位長(zhǎng)度的前提下,將每位的數(shù)字都向左移動(dòng),左邊移出去的直接丟棄,右邊空出來(lái)的位置用0填充。無(wú)符號(hào)就是保持符號(hào)位不變,即本來(lái)是正數(shù),移位后一樣為正數(shù)。
正數(shù)的無(wú)符號(hào)左移位運(yùn)算
這里以 7 << 2 為例。
首先將7轉(zhuǎn)為二進(jìn)制是 0000 0000 0000 0000 0000 0000 0000 0111.
然后對(duì)其向左移兩位.
0000 0000 0000 0000 0000 0000 0000 0111 --> 7
<< 2 00 0000 0000 0000 0000 0000 0000 0001 11 --> 左邊超出部分移除,坐標(biāo)填充0
----------------------------------------------------
= 0000 0000 0000 0000 0000 0000 0001 1100 --> 28
得到值為 0000 0000 0000 0000 0000 0000 0001 1100.
轉(zhuǎn)換為十進(jìn)制為 28.即 7 << 2 = 28。
然后我們對(duì)以上的運(yùn)算過(guò)程做一個(gè)處理,將這些二進(jìn)制轉(zhuǎn)換為我們熟悉的十進(jìn)制。
0000 0000 0000 0000 0000 0000 0000 0111 = 2^2 + 2^1 + 2^0 = 7
<< 2 00 0000 0000 0000 0000 0000 0000 0001 1100 = 2^4 + 2^3 + 2^2 = 28
對(duì)移位后的算式進(jìn)行合并項(xiàng)可得到 2^4 + 2^3 + 2^2 = (2^2 + 2^1 + 2^0) * 2^2,即 2^4 + 2^3 + 2^2 = (2^2 + 2^1 + 2^0) * 2^2 = 7 * 2^2。由此我們可得出 7 << 2 = 7 * 2^2 = 28。
我們通過(guò)計(jì)算幾個(gè)簡(jiǎn)單的左移位運(yùn)算,與標(biāo)準(zhǔn)答案進(jìn)行比較,驗(yàn)證一下這個(gè)結(jié)論。
4 << 1 = 4 * 2^1 = 8
12 << 1 = 12 * 2^1 = 24
66 << 3 = 66 * 2^3 = 528
100 << 5 = 100 * 2^5 = 3200
9999 << 6 = 9999 * 2^6 = 639936
在控制臺(tái)中以上幾個(gè)算式的結(jié)果為

答案完全一致。說(shuō)明我們的結(jié)論是正確的。當(dāng)然這個(gè)結(jié)論僅限于那些二進(jìn)制移位不會(huì)左移移出的數(shù)字的簡(jiǎn)單運(yùn)算。當(dāng)我們遇到一些簡(jiǎn)單的可以口算的左移位運(yùn)算時(shí)就可以使用這個(gè)結(jié)論快速得到結(jié)果,如果對(duì)于 99999 << 66 這種較復(fù)雜的運(yùn)算你也用這個(gè)結(jié)論計(jì)算,也沒(méi)有人會(huì)介意。
負(fù)數(shù)的無(wú)符號(hào)左移位運(yùn)算
下面我們看一下負(fù)數(shù)的左移位運(yùn)算。以 -66 << 2 為例。
首先,我們先復(fù)習(xí)一下負(fù)數(shù)如何轉(zhuǎn)換為二進(jìn)制。
負(fù)數(shù)轉(zhuǎn)換為二進(jìn)制的步驟有三:
- 確定負(fù)數(shù)對(duì)應(yīng)正數(shù)的二進(jìn)制
- 求第一步得到的二進(jìn)制的反碼
- 第二步得到的二進(jìn)制加一
-66
66 -> 0000 0000 0000 0000 0000 0000 0100 0010
反碼 -> 1111 1111 1111 1111 1111 1111 1011 1101
+1 -> 1111 1111 1111 1111 1111 1111 1011 1110
然后對(duì)其向左移兩位.
1111 1111 1111 1111 1111 1111 1011 1110 --> -66
<< 2 11 1111 1111 1111 1111 1111 1110 1111 10 --> 左邊超出部分移除,坐標(biāo)填充0
----------------------------------------------------
= 1111 1111 1111 1111 1111 1110 1111 1000 --> 28
得到值為 1111 1111 1111 1111 1111 1110 1111 1000.然后我們將其轉(zhuǎn)換成十進(jìn)制。
1111 1111 1111 1111 1111 1110 1111 1000
-1 -> 1111 1111 1111 1111 1111 1110 1111 0111
反碼 -> 0000 0000 0000 0000 0000 0001 0000 1000
轉(zhuǎn)換 -> -264 --> 別忘了帶符號(hào)
轉(zhuǎn)換為十進(jìn)制為 -264.即 -66 << 2 = -264。
拓展
- 拓展1
剛剛我們計(jì)算 -66 的二進(jìn)制得到的是 1111 1111 1111 1111 1111 1111 1011 1110。我們?cè)诳刂婆_(tái)驗(yàn)證一下我們得到的這個(gè)二進(jìn)制。

欸,這差距有點(diǎn)大呀。是我們算的不對(duì)嗎?肯定不是。是因?yàn)閖s的引擎在做處理的時(shí)候是先按無(wú)符號(hào)數(shù)字進(jìn)行處理,轉(zhuǎn)換完了才會(huì)加上符號(hào)。所以 -66 的二進(jìn)制應(yīng)該是先得出66的二進(jìn)制,然后加上負(fù)號(hào),就得到了
-1000010。
- 拓展2
我們比較一下下面幾個(gè)算式。
33 << 2 = 132
-33 << 2 = -132
66 << 2 = 264
-66 << 2 = -264
999 << 2 = 3996
-999 << 2 = -3996
是的沒(méi)錯(cuò),進(jìn)行無(wú)符號(hào)左移位運(yùn)算時(shí),當(dāng)兩個(gè)數(shù)的絕對(duì)值相等時(shí),其相同位數(shù)的移位的絕對(duì)值一定相等。
>>
“>>”運(yùn)算符執(zhí)行有符號(hào)右移位運(yùn)算。與左移運(yùn)算操作相反,它把 32 位數(shù)字中的所有有效位整體右移,再使用符號(hào)位的值填充空位。移動(dòng)過(guò)程中超出的值將被丟棄。
正數(shù)的有符號(hào)右移位運(yùn)算
這里以 666 >> 3 為例。
首先將666轉(zhuǎn)換為二進(jìn)制是 0000 0000 0000 0000 0000 0010 1001 1010。
然后對(duì)其向右移三位。
0000 0000 0000 0000 0000 0010 1001 1010 --> 666
>> 3 0 0000 0000 0000 0000 0000 0101 0011 010 --> 因?yàn)槭钦龜?shù)所以左邊填充0,右邊超出部分移除
------------------------------------------------
= 0000 0000 0000 0000 0000 0000 0101 0011 --> 83
得到值為 0000 0000 0000 0000 0000 0000 0101 0011.
轉(zhuǎn)換為十進(jìn)制為 83.即 666 >> 3 = 83。
然后我們對(duì)以上的運(yùn)算過(guò)程做一個(gè)處理,將這些二進(jìn)制轉(zhuǎn)換為我們熟悉的十進(jìn)制。
0000 0000 0000 0000 0000 0010 1001 1010 = 2^9 + 2^7 + 2^4 + 2^3 + 2^1 = 666
>> 3 0 0000 0000 0000 0000 0000 0101 0011 010 = 2^6 + 2^4 + 2^1 + 2^0 = 28
這個(gè)規(guī)律好像不太好總結(jié)?
負(fù)數(shù)的有符號(hào)右移位運(yùn)算
這里以 -666 >> 3為例。
因?yàn)槭怯蟹?hào)的運(yùn)算,所以這里不再適用上一小節(jié)說(shuō)的js的特殊處理。先將-666轉(zhuǎn)換為二進(jìn)制。
-666
666 -> 0000 0000 0000 0000 0000 0010 1001 1010
反碼 -> 1111 1111 1111 1111 1111 1101 0110 0101
+1 -> 1111 1111 1111 1111 1111 1101 0110 0110
即-666的二進(jìn)制形式為 1111 1111 1111 1111 1111 1101 0110 0110,然后對(duì)其進(jìn)行有符號(hào)右移位運(yùn)算
1111 1111 1111 1111 1111 1101 0110 0110 --> -666
>> 3 1 1111 1111 1111 1111 1111 1010 1100 110 --> 因?yàn)槭秦?fù)數(shù)所以左邊填充1,右邊超出部分移除
------------------------------------------------
= 1111 1111 1111 1111 1111 1111 1010 1100 --> -84
移位后得到的值為 1111 1111 1111 1111 1111 1111 1010 1100,是一個(gè)負(fù)值,我們將其轉(zhuǎn)成十進(jìn)制。
1111 1111 1111 1111 1111 1111 1010 1100
-1 -> 1111 1111 1111 1111 1111 1111 1010 1011
反碼 -> 0000 0000 0000 0000 0000 0000 0101 0100
轉(zhuǎn)換 -> -84 --> 別忘了帶符號(hào)
我們對(duì)此結(jié)果進(jìn)行驗(yàn)證。

可見(jiàn),我們的運(yùn)算是完全正確的。
>>>
“>>>”運(yùn)算符執(zhí)行無(wú)符號(hào)右移位運(yùn)算。它把無(wú)符號(hào)的 32 位整數(shù)所有效位整體右移。對(duì)于無(wú)符號(hào)數(shù)或正數(shù)右移運(yùn)算,無(wú)符號(hào)右移與有符號(hào)右移運(yùn)算的結(jié)果是相同的。
正數(shù)無(wú)符號(hào)右移位運(yùn)算
這里我們以 666 >>> 3為例。
首先將666轉(zhuǎn)換為二進(jìn)制是 0000 0000 0000 0000 0000 0010 1001 1010。
然后對(duì)其向右移三位。
0000 0000 0000 0000 0000 0010 1001 1010 --> 666
>> 3 0 0000 0000 0000 0000 0000 0101 0011 010 --> 左邊空出部分填充0,右邊超出部分移除
------------------------------------------------
= 0000 0000 0000 0000 0000 0000 0101 0011 --> 83
得到值為 0000 0000 0000 0000 0000 0000 0101 0011.
轉(zhuǎn)換為十進(jìn)制為 83.即 666 >> 3 = 83。
負(fù)數(shù)無(wú)符號(hào)右移位運(yùn)算
這里以 -666 >> 3為例。
因?yàn)槭怯蟹?hào)的運(yùn)算,所以這里不再適用上一小節(jié)說(shuō)的js的特殊處理。先將-666轉(zhuǎn)換為二進(jìn)制。
-666
666 -> 0000 0000 0000 0000 0000 0010 1001 1010
反碼 -> 1111 1111 1111 1111 1111 1101 0110 0101
+1 -> 1111 1111 1111 1111 1111 1101 0110 0110
即-666的二進(jìn)制形式為 1111 1111 1111 1111 1111 1101 0110 0110,然后對(duì)其進(jìn)行有符號(hào)右移位運(yùn)算
1111 1111 1111 1111 1111 1101 0110 0110 --> -666
>> 3 1 1111 1111 1111 1111 1111 1010 1100 110 --> 左邊空出部分填充0,右邊超出部分移除
------------------------------------------------
= 0001 1111 1111 1111 1111 1111 1010 1100 --> 536870828
移位后得到的值為 0001 1111 1111 1111 1111 1111 1010 1100,轉(zhuǎn)成十進(jìn)制為536870828。
是不是超級(jí)大。因?yàn)槭菬o(wú)符號(hào)右移位運(yùn)算,所以在左邊空出部分不論正負(fù)都會(huì)填充0.
我們對(duì)此結(jié)果進(jìn)行驗(yàn)證。

可見(jiàn),我們的運(yùn)算是完全正確的。
注意:因?yàn)閷?duì)負(fù)數(shù)進(jìn)行無(wú)符號(hào)右移位運(yùn)算時(shí),所得結(jié)果很大,所以在使用過(guò)程中需要格外注意。
疑問(wèn):左移位和右移位根本都是只對(duì)位置進(jìn)行了移動(dòng),那么對(duì)于 x1 >> k = y1 和 y2 << k = x2 中的 x1 等于 x2,y1 等于 y2 嗎?
不一定。因?yàn)槲覀儾荒艽_保移動(dòng)過(guò)程中被丟棄的值均為0。但凡有一個(gè)1被丟棄,就不會(huì)相等。而如果被丟棄的都是0,那么 x1 === x2 y1 === y2。如下圖所示。

邏輯位運(yùn)算符
&
“&”運(yùn)算符(位與)用于對(duì)兩個(gè)二進(jìn)制操作數(shù)逐位進(jìn)行比較,并根據(jù)下表所示的換算表返回結(jié)果。
| 第一個(gè)數(shù)的位值 | 第二個(gè)數(shù)的位值 | 運(yùn)算結(jié)果 |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 0 |
這里以 66 & 33 為例。
首先將兩個(gè)數(shù)轉(zhuǎn)換為二進(jìn)制是 0000 0000 0000 0000 0000 0000 0100 0010 和 0000 0000 0000 0000 0000 0000 0010 0001。
然后對(duì)其進(jìn)行與運(yùn)算。
0000 0000 0000 0000 0000 0000 0100 0010
& 0000 0000 0000 0000 0000 0000 0010 0001
-------------------------------------------- --> 按上述表格進(jìn)行運(yùn)算
0000 0000 0000 0000 0000 0000 0000 0000 --> 0
得出結(jié)果為 0.
負(fù)數(shù)的與運(yùn)算與正數(shù)并無(wú)區(qū)別,不做討論。
|
“|”運(yùn)算符(位或)用于對(duì)兩個(gè)二進(jìn)制操作數(shù)逐位進(jìn)行比較,并根據(jù)如表格所示的換算表返回結(jié)果。
| 第一個(gè)數(shù)的位值 | 第二個(gè)數(shù)的位值 | 運(yùn)算結(jié)果 |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
這里以 66 | 66 為例。
首先將兩個(gè)數(shù)轉(zhuǎn)換為二進(jìn)制是 0000 0000 0000 0000 0000 0000 0100 0010 和 0000 0000 0000 0000 0000 0000 0100 0010。
然后對(duì)其進(jìn)行與運(yùn)算。
0000 0000 0000 0000 0000 0000 0100 0010
| 0000 0000 0000 0000 0000 0000 0100 0010
-------------------------------------------- --> 按上述表格進(jìn)行運(yùn)算
0000 0000 0000 0000 0000 0000 0100 0010 --> 66
得出結(jié)果為 66.
負(fù)數(shù)的與運(yùn)算與正數(shù)并無(wú)區(qū)別,不做討論。
^
“^”運(yùn)算符(位異或)用于對(duì)兩個(gè)二進(jìn)制操作數(shù)逐位進(jìn)行比較,并根據(jù)如表格所示的換算表返回結(jié)果。
| 第一個(gè)數(shù)的位值 | 第二個(gè)數(shù)的位值 | 運(yùn)算結(jié)果 |
|---|---|---|
| 1 | 1 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
這里以 66 ^ 66 為例。
首先將兩個(gè)數(shù)轉(zhuǎn)換為二進(jìn)制是 0000 0000 0000 0000 0000 0000 0100 0010 和 0000 0000 0000 0000 0000 0000 0100 0010。
然后對(duì)其進(jìn)行與運(yùn)算。
0000 0000 0000 0000 0000 0000 0100 0010
| 0000 0000 0000 0000 0000 0000 0100 0010
-------------------------------------------- --> 按上述表格進(jìn)行運(yùn)算
0000 0000 0000 0000 0000 0000 0000 0000 --> 0
得出結(jié)果為 0.
負(fù)數(shù)的與運(yùn)算與正數(shù)并無(wú)區(qū)別,不做討論。
~
“~”運(yùn)算符(位非)用于對(duì)一個(gè)二進(jìn)制操作數(shù)逐位進(jìn)行取反操作。
這里以 ~66 為例。
首先將其轉(zhuǎn)換為二進(jìn)制是 0000 0000 0000 0000 0000 0000 0100 0010。
然后對(duì)其進(jìn)行與運(yùn)算。
~ 0000 0000 0000 0000 0000 0000 0100 0010
-------------------------------------------- --> 對(duì)每一位都進(jìn)行取反操作
1111 1111 1111 1111 1111 1111 1011 1101 --> -67
將結(jié)果(1111 1111 1111 1111 1111 1111 1011 1101)轉(zhuǎn)換為十進(jìn)制
1111 1111 1111 1111 1111 1111 1011 1101
-1 -> 1111 1111 1111 1111 1111 1111 1011 1100
反碼 -> 0000 0000 0000 0000 0000 0000 0100 0011
轉(zhuǎn)換 -> -67 --> 別忘了符號(hào)
得出結(jié)果為 -67.
這里我們?cè)傥铱磶讉€(gè)例子。
~2 = -3
~10 = -11
~66 = -67
~99 = -100
~-99 = 98
從中我們可以看出,位非操作就是對(duì)數(shù)字加一,然后取負(fù)。我們可以寫(xiě)個(gè)簡(jiǎn)單的判斷方法來(lái)驗(yàn)證。
function judgeResult(num) {
return ~num === -(num + 1)
}
judgeResult() // false
judgeResult(10) // true
judgeResult(1) // true
judgeResult(-66) // true
judgeResult(324) // true
總結(jié)
位運(yùn)算符運(yùn)算結(jié)果非常有趣,在平時(shí)可以多加應(yīng)用,但是一定要注意可能產(chǎn)生大數(shù)的預(yù)算,避免產(chǎn)生不必要的BUG。
這篇文章只是做了一個(gè)簡(jiǎn)單的介紹。后面有空了會(huì)做一下在實(shí)際開(kāi)發(fā)中的應(yīng)用,雖然我可能很久都遇不到。