JavaScript 二進(jìn)制位運(yùn)算符

2023-03-20 15:57 更新

概述

二進(jìn)制位運(yùn)算符用于直接對(duì)二進(jìn)制位進(jìn)行計(jì)算,一共有7個(gè)。

  • 二進(jìn)制或運(yùn)算符(or):符號(hào)為|,表示若兩個(gè)二進(jìn)制位都為0,則結(jié)果為0,否則為1。
  • 二進(jìn)制與運(yùn)算符(and):符號(hào)為&,表示若兩個(gè)二進(jìn)制位都為1,則結(jié)果為1,否則為0。
  • 二進(jìn)制否運(yùn)算符(not):符號(hào)為~,表示對(duì)一個(gè)二進(jìn)制位取反。
  • 異或運(yùn)算符(xor):符號(hào)為^,表示若兩個(gè)二進(jìn)制位不相同,則結(jié)果為1,否則為0。
  • 左移運(yùn)算符(left shift):符號(hào)為<<,詳見下文解釋。
  • 右移運(yùn)算符(right shift):符號(hào)為>>,詳見下文解釋。
  • 頭部補(bǔ)零的右移運(yùn)算符(zero filled right shift):符號(hào)為>>>,詳見下文解釋。

這些位運(yùn)算符直接處理每一個(gè)比特位(bit),所以是非常底層的運(yùn)算,好處是速度極快,缺點(diǎn)是很不直觀,許多場(chǎng)合不能使用它們,否則會(huì)使代碼難以理解和查錯(cuò)。

有一點(diǎn)需要特別注意,位運(yùn)算符只對(duì)整數(shù)起作用,如果一個(gè)運(yùn)算子不是整數(shù),會(huì)自動(dòng)轉(zhuǎn)為整數(shù)后再執(zhí)行。另外,雖然在 JavaScript 內(nèi)部,數(shù)值都是以64位浮點(diǎn)數(shù)的形式儲(chǔ)存,但是做位運(yùn)算的時(shí)候,是以32位帶符號(hào)的整數(shù)進(jìn)行運(yùn)算的,并且返回值也是一個(gè)32位帶符號(hào)的整數(shù)。

i = i | 0;

上面這行代碼的意思,就是將i(不管是整數(shù)或小數(shù))轉(zhuǎn)為32位整數(shù)。

利用這個(gè)特性,可以寫出一個(gè)函數(shù),將任意數(shù)值轉(zhuǎn)為32位整數(shù)。

function toInt32(x) {
  return x | 0;
}

上面這個(gè)函數(shù)將任意值與0進(jìn)行一次或運(yùn)算,這個(gè)位運(yùn)算會(huì)自動(dòng)將一個(gè)值轉(zhuǎn)為32位整數(shù)。下面是這個(gè)函數(shù)的用法。

toInt32(1.001) // 1
toInt32(1.999) // 1
toInt32(1) // 1
toInt32(-1) // -1
toInt32(Math.pow(2, 32) + 1) // 1
toInt32(Math.pow(2, 32) - 1) // -1

上面代碼中,toInt32可以將小數(shù)轉(zhuǎn)為整數(shù)。對(duì)于一般的整數(shù),返回值不會(huì)有任何變化。對(duì)于大于或等于2的32次方的整數(shù),大于32位的數(shù)位都會(huì)被舍去。

二進(jìn)制或運(yùn)算符

二進(jìn)制或運(yùn)算符(|)逐位比較兩個(gè)運(yùn)算子,兩個(gè)二進(jìn)制位之中只要有一個(gè)為1,就返回1,否則返回0。

0 | 3 // 3

上面代碼中,03的二進(jìn)制形式分別是0011,所以進(jìn)行二進(jìn)制或運(yùn)算會(huì)得到11(即3)。

位運(yùn)算只對(duì)整數(shù)有效,遇到小數(shù)時(shí),會(huì)將小數(shù)部分舍去,只保留整數(shù)部分。所以,將一個(gè)小數(shù)與0進(jìn)行二進(jìn)制或運(yùn)算,等同于對(duì)該數(shù)去除小數(shù)部分,即取整數(shù)位。

2.9 | 0 // 2
-2.9 | 0 // -2

需要注意的是,這種取整方法不適用超過(guò)32位整數(shù)最大值2147483647的數(shù)。

2147483649.4 | 0;
// -2147483647

二進(jìn)制與運(yùn)算符

二進(jìn)制與運(yùn)算符(&)的規(guī)則是逐位比較兩個(gè)運(yùn)算子,兩個(gè)二進(jìn)制位之中只要有一個(gè)位為0,就返回0,否則返回1。

0 & 3 // 0

上面代碼中,0(二進(jìn)制00)和3(二進(jìn)制11)進(jìn)行二進(jìn)制與運(yùn)算會(huì)得到00(即0)。

二進(jìn)制否運(yùn)算符

二進(jìn)制否運(yùn)算符(~)將每個(gè)二進(jìn)制位都變?yōu)橄喾粗担?code>0變?yōu)?code>1,1變?yōu)?code>0)。它的返回結(jié)果有時(shí)比較難理解,因?yàn)樯婕暗接?jì)算機(jī)內(nèi)部的數(shù)值表示機(jī)制。

~ 3 // -4

上面表達(dá)式對(duì)3進(jìn)行二進(jìn)制否運(yùn)算,得到-4。之所以會(huì)有這樣的結(jié)果,是因?yàn)槲贿\(yùn)算時(shí),JavaScript 內(nèi)部將所有的運(yùn)算子都轉(zhuǎn)為32位的二進(jìn)制整數(shù)再進(jìn)行運(yùn)算。

3的32位整數(shù)形式是00000000000000000000000000000011,二進(jìn)制否運(yùn)算以后得到11111111111111111111111111111100。由于第一位(符號(hào)位)是1,所以這個(gè)數(shù)是一個(gè)負(fù)數(shù)。JavaScript 內(nèi)部采用補(bǔ)碼形式表示負(fù)數(shù),即需要將這個(gè)數(shù)減去1,再取一次反,然后加上負(fù)號(hào),才能得到這個(gè)負(fù)數(shù)對(duì)應(yīng)的10進(jìn)制值。這個(gè)數(shù)減去1等于11111111111111111111111111111011,再取一次反得到00000000000000000000000000000100,再加上負(fù)號(hào)就是-4??紤]到這樣的過(guò)程比較麻煩,可以簡(jiǎn)單記憶成,一個(gè)數(shù)與自身的取反值相加,等于-1。

~ -3 // 2

上面表達(dá)式可以這樣算,-3的取反值等于-1減去-3,結(jié)果為2

對(duì)一個(gè)整數(shù)連續(xù)兩次二進(jìn)制否運(yùn)算,得到它自身。

~~3 // 3

所有的位運(yùn)算都只對(duì)整數(shù)有效。二進(jìn)制否運(yùn)算遇到小數(shù)時(shí),也會(huì)將小數(shù)部分舍去,只保留整數(shù)部分。所以,對(duì)一個(gè)小數(shù)連續(xù)進(jìn)行兩次二進(jìn)制否運(yùn)算,能達(dá)到取整效果。

~~2.9 // 2
~~47.11 // 47
~~1.9999 // 1
~~3 // 3

使用二進(jìn)制否運(yùn)算取整,是所有取整方法中最快的一種。

對(duì)字符串進(jìn)行二進(jìn)制否運(yùn)算,JavaScript 引擎會(huì)先調(diào)用Number函數(shù),將字符串轉(zhuǎn)為數(shù)值。

// 相當(dāng)于~Number('011')
~'011'  // -12

// 相當(dāng)于~Number('42 cats')
~'42 cats' // -1

// 相當(dāng)于~Number('0xcafebabe')
~'0xcafebabe' // 889275713

// 相當(dāng)于~Number('deadbeef')
~'deadbeef' // -1

Number函數(shù)將字符串轉(zhuǎn)為數(shù)值的規(guī)則,參見《數(shù)據(jù)的類型轉(zhuǎn)換》一章。

對(duì)于其他類型的值,二進(jìn)制否運(yùn)算也是先用Number轉(zhuǎn)為數(shù)值,然后再進(jìn)行處理。

// 相當(dāng)于 ~Number([])
~[] // -1

// 相當(dāng)于 ~Number(NaN)
~NaN // -1

// 相當(dāng)于 ~Number(null)
~null // -1

異或運(yùn)算符

異或運(yùn)算(^)在兩個(gè)二進(jìn)制位不同時(shí)返回1,相同時(shí)返回0。

0 ^ 3 // 3

上面表達(dá)式中,0(二進(jìn)制00)與3(二進(jìn)制11)進(jìn)行異或運(yùn)算,它們每一個(gè)二進(jìn)制位都不同,所以得到11(即3)。

“異或運(yùn)算”有一個(gè)特殊運(yùn)用,連續(xù)對(duì)兩個(gè)數(shù)ab進(jìn)行三次異或運(yùn)算,a^=b; b^=a; a^=b;,可以互換它們的值。這意味著,使用“異或運(yùn)算”可以在不引入臨時(shí)變量的前提下,互換兩個(gè)變量的值。

var a = 10;
var b = 99;

a ^= b, b ^= a, a ^= b;

a // 99
b // 10

這是互換兩個(gè)變量的值的最快方法。

異或運(yùn)算也可以用來(lái)取整。

12.9 ^ 0 // 12

左移運(yùn)算符 

左移運(yùn)算符(<<)表示將一個(gè)數(shù)的二進(jìn)制值向左移動(dòng)指定的位數(shù),尾部補(bǔ)0,即乘以2的指定次方。向左移動(dòng)的時(shí)候,最高位的符號(hào)位是一起移動(dòng)的。

// 4 的二進(jìn)制形式為100,
// 左移一位為1000(即十進(jìn)制的8)
// 相當(dāng)于乘以2的1次方
4 << 1
// 8

-4 << 1
// -8

上面代碼中,-4左移一位得到-8,是因?yàn)?code>-4的二進(jìn)制形式是11111111111111111111111111111100,左移一位后得到11111111111111111111111111111000,該數(shù)轉(zhuǎn)為十進(jìn)制(減去1后取反,再加上負(fù)號(hào))即為-8。

如果左移0位,就相當(dāng)于將該數(shù)值轉(zhuǎn)為32位整數(shù),等同于取整,對(duì)于正數(shù)和負(fù)數(shù)都有效。

13.5 << 0
// 13

-13.5 << 0
// -13

左移運(yùn)算符用于二進(jìn)制數(shù)值非常方便。

var color = {r: 186, g: 218, b: 85};

// RGB to HEX
// (1 << 24)的作用為保證結(jié)果是6位數(shù)
var rgb2hex = function(r, g, b) {
  return '#' + ((1 << 24) + (r << 16) + (g << 8) + b)
    .toString(16) // 先轉(zhuǎn)成十六進(jìn)制,然后返回字符串
    .substr(1);   // 去除字符串的最高位,返回后面六個(gè)字符串
}

rgb2hex(color.r, color.g, color.b)
// "#bada55"

上面代碼使用左移運(yùn)算符,將顏色的 RGB 值轉(zhuǎn)為 HEX 值。

右移運(yùn)算符

右移運(yùn)算符(>>)表示將一個(gè)數(shù)的二進(jìn)制值向右移動(dòng)指定的位數(shù)。如果是正數(shù),頭部全部補(bǔ)0;如果是負(fù)數(shù),頭部全部補(bǔ)1。右移運(yùn)算符基本上相當(dāng)于除以2的指定次方(最高位即符號(hào)位參與移動(dòng))。

4 >> 1
// 2
/*
// 因?yàn)?的二進(jìn)制形式為 00000000000000000000000000000100,
// 右移一位得到 00000000000000000000000000000010,
// 即為十進(jìn)制的2
*/

-4 >> 1
// -2
/*
// 因?yàn)?4的二進(jìn)制形式為 11111111111111111111111111111100,
// 右移一位,頭部補(bǔ)1,得到 11111111111111111111111111111110,
// 即為十進(jìn)制的-2
*/

右移運(yùn)算可以模擬 2 的整除運(yùn)算。

5 >> 1
// 2
// 相當(dāng)于 5 / 2 = 2

21 >> 2
// 5
// 相當(dāng)于 21 / 4 = 5

21 >> 3
// 2
// 相當(dāng)于 21 / 8 = 2

21 >> 4
// 1
// 相當(dāng)于 21 / 16 = 1

頭部補(bǔ)零的右移運(yùn)算符

頭部補(bǔ)零的右移運(yùn)算符(>>>)與右移運(yùn)算符(>>)只有一個(gè)差別,就是一個(gè)數(shù)的二進(jìn)制形式向右移動(dòng)時(shí),頭部一律補(bǔ)零,而不考慮符號(hào)位。所以,該運(yùn)算總是得到正值。對(duì)于正數(shù),該運(yùn)算的結(jié)果與右移運(yùn)算符(>>)完全一致,區(qū)別主要在于負(fù)數(shù)。

4 >>> 1
// 2

-4 >>> 1
// 2147483646
/*
// 因?yàn)?4的二進(jìn)制形式為11111111111111111111111111111100,
// 帶符號(hào)位的右移一位,得到01111111111111111111111111111110,
// 即為十進(jìn)制的2147483646。
*/

這個(gè)運(yùn)算實(shí)際上將一個(gè)值轉(zhuǎn)為32位無(wú)符號(hào)整數(shù)。

查看一個(gè)負(fù)整數(shù)在計(jì)算機(jī)內(nèi)部的儲(chǔ)存形式,最快的方法就是使用這個(gè)運(yùn)算符。

-1 >>> 0 // 4294967295

上面代碼表示,-1作為32位整數(shù)時(shí),內(nèi)部的儲(chǔ)存形式使用無(wú)符號(hào)整數(shù)格式解讀,值為 4294967295(即(2^32)-1,等于11111111111111111111111111111111)。

開關(guān)作用

位運(yùn)算符可以用作設(shè)置對(duì)象屬性的開關(guān)。

假定某個(gè)對(duì)象有四個(gè)開關(guān),每個(gè)開關(guān)都是一個(gè)變量。那么,可以設(shè)置一個(gè)四位的二進(jìn)制數(shù),它的每個(gè)位對(duì)應(yīng)一個(gè)開關(guān)。

var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000

上面代碼設(shè)置 A、B、C、D 四個(gè)開關(guān),每個(gè)開關(guān)分別占有一個(gè)二進(jìn)制位。

然后,就可以用二進(jìn)制與運(yùn)算,檢查當(dāng)前設(shè)置是否打開了指定開關(guān)。

var flags = 5; // 二進(jìn)制的0101

if (flags & FLAG_C) {
  // ...
}
// 0101 & 0100 => 0100 => true

上面代碼檢驗(yàn)是否打開了開關(guān)C。如果打開,會(huì)返回true,否則返回false。

現(xiàn)在假設(shè)需要打開AB、D三個(gè)開關(guān),我們可以構(gòu)造一個(gè)掩碼變量。

var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011

上面代碼對(duì)A、BD三個(gè)變量進(jìn)行二進(jìn)制或運(yùn)算,得到掩碼值為二進(jìn)制的1011。

有了掩碼,二進(jìn)制或運(yùn)算可以確保打開指定的開關(guān)。

flags = flags | mask;

上面代碼中,計(jì)算后得到的flags變量,代表三個(gè)開關(guān)的二進(jìn)制位都打開了。

二進(jìn)制與運(yùn)算可以將當(dāng)前設(shè)置中凡是與開關(guān)設(shè)置不一樣的項(xiàng),全部關(guān)閉。

flags = flags & mask;

異或運(yùn)算可以切換(toggle)當(dāng)前設(shè)置,即第一次執(zhí)行可以得到當(dāng)前設(shè)置的相反值,再執(zhí)行一次又得到原來(lái)的值。

flags = flags ^ mask;

二進(jìn)制否運(yùn)算可以翻轉(zhuǎn)當(dāng)前設(shè)置,即原設(shè)置為0,運(yùn)算后變?yōu)?code>1;原設(shè)置為1,運(yùn)算后變?yōu)?code>0。

flags = ~flags;

參考鏈接


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)