ECMAScript 位運算符

2018-06-20 13:54 更新

位運算符是在數(shù)字底層(即表示數(shù)字的 32 個數(shù)位)進行操作的。

重溫整數(shù)

ECMAScript整數(shù)有兩種類型,即有符號整數(shù)(允許用正數(shù)和負數(shù))和無符號整數(shù)(只允許用正數(shù))。在ECMAScript中,所有整數(shù)字面量默認都是有符號整數(shù),這意味著什么呢?

有符號整數(shù)使用31位表示整數(shù)的數(shù)值,用第32位表示整數(shù)的符號,0表示正數(shù),1表示負數(shù)。數(shù)值范圍從-2147483648到2147483647。

可以以兩種不同的方式存儲二進制形式的有符號整數(shù),一種用于存儲正數(shù),一種用于存儲負數(shù)。正數(shù)是以真二進制形式存儲的,前31位中的每一位都表示2的冪,從第1 位(位 0)開始,表示20,第2位(位1)表示21。沒用到的位用0填充,即忽略不計。例如,下圖展示的是數(shù)18的表示法。

32 位二進制表示的有符號整數(shù)

18的二進制版本只用了前5位,它們是這個數(shù)字的有效位。把數(shù)字轉(zhuǎn)換成二進制字符串,就能看到有效位:

var iNum = 18;
alert(iNum.toString(2));	//輸出 "10010"

這段代碼只輸出"10010",而不是18的32位表示。其他的數(shù)位并不重要,因為僅使用前5位即可確定這個十進制數(shù)值。如下圖所示:

5 位二進制表示的整數(shù) 18

負數(shù)也存儲為二進制代碼,不過采用的形式是二進制補碼。計算數(shù)字二進制補碼的步驟有三步:

  1. 確定該數(shù)字的非負版本的二進制表示(例如,要計算-18的二進制補碼,首先要確定18的二進制表示)
  2. 求得二進制反碼,即要把0替換為1,把1替換為0
  3. 在二進制反碼上加1

要確定-18的二進制表示,首先必須得到18的二進制表示,如下所示:

0000 0000 0000 0000 0000 0000 0001 0010

接下來,計算二進制反碼,如下所示:

1111 1111 1111 1111 1111 1111 1110 1101

最后,在二進制反碼上加1,如下所示:

1111 1111 1111 1111 1111 1111 1110 1101
                                      1
---------------------------------------
1111 1111 1111 1111 1111 1111 1110 1110

因此,-18的二進制表示即1111 1111 1111 1111 1111 1111 1110 1110。記住,在處理有符號整數(shù)時,開發(fā)者不能訪問31位。

有趣的是,把負整數(shù)轉(zhuǎn)換成二進制字符串后,ECMAScript并不以二進制補碼的形式顯示,而是用數(shù)字絕對值的標(biāo)準二進制代碼前面加負號的形式輸出。例如:

var iNum = -18;
alert(iNum.toString(2));	//輸出 "-10010"

這段代碼輸出的是"-10010",而非二進制補碼,這是為避免訪問位31。為了簡便,ECMAScript用一種簡單的方式處理整數(shù),使得開發(fā)者不必關(guān)心它們的用法。

另一方面,無符號整數(shù)把最后一位作為另一個數(shù)位處理。在這種模式中,第32位不表示數(shù)字的符號,而是值231。由于這個額外的位,無符號整數(shù)的數(shù)值范圍為 0 到 4294967295。對于小于2147483647的整數(shù)來說,無符號整數(shù)看來與有符號整數(shù)一樣,而大于2147483647的整數(shù)則要使用位31(在有符號整數(shù)中,這一位總是 0)。

把無符號整數(shù)轉(zhuǎn)換成字符串后,只返回它們的有效位。

注意:所有整數(shù)字面量都默認存儲為有符號整數(shù)。只有ECMAScript的位運算符才能創(chuàng)建無符號整數(shù)。

位運算 NOT

位運算NOT由否定號(~)表示,它是ECMAScript中為數(shù)不多的與二進制算術(shù)有關(guān)的運算符之一。

位運算NOT是三步的處理過程:

  1. 把運算數(shù)轉(zhuǎn)換成32位數(shù)字
  2. 把二進制數(shù)轉(zhuǎn)換成它的二進制反碼
  3. 把二進制數(shù)轉(zhuǎn)換成浮點數(shù)

例如:

var iNum1 = 25;		//25 等于 00000000000000000000000000011001
var iNum2 = ~iNum1;
	//轉(zhuǎn)換為 11111111111111111111111111100110
alert(iNum2);		//輸出 "-26"

位運算NOT實質(zhì)上是對數(shù)字求負,然后減1,因此25變-26。用下面的方法也可以得到同樣的方法:

var iNum1 = 25;
var iNum2 = -iNum1 -1;
alert(iNum2);	//輸出 -26

位運算 AND

位運算AND由和號(&)表示,直接對數(shù)字的二進制形式進行運算。它把每個數(shù)字中的數(shù)位對齊,然后用下面的規(guī)則對同一位置上的兩個數(shù)位進行AND運算:

第一個數(shù)字中的數(shù)位第二個數(shù)字中的數(shù)位結(jié)果
111
100
010
000

例如,要對數(shù)字25和3進行AND運算,代碼如下所示:

var iResult = 25 & 3;
alert(iResult);	//輸出 "1"

25和3進行AND運算的結(jié)果是1。為什么?分析如下:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

可以看出,在25和3中,只有一個數(shù)位(位0)存放的都是 1,因此,其他數(shù)位生成的都是0,所以結(jié)果為1。

位運算 OR

位運算OR由符號(|)表示,也是直接對數(shù)字的二進制形式進行運算。在計算每位時,OR運算符采用下列規(guī)則:

第一個數(shù)字中的數(shù)位第二個數(shù)字中的數(shù)位結(jié)果
111
101
011
000

仍然使用AND運算符所用的例子,對25和3進行OR運算,代碼如下:

var iResult = 25 | 3;
alert(iResult);	//輸出 "27"

25和3進行OR運算的結(jié)果是27:

25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3 = 0000 0000 0000 0000 0000 0000 0000 0011
--------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

可以看出,在兩個數(shù)字中,共有4個數(shù)位存放的是1,這些數(shù)位被傳遞給結(jié)果。二進制代碼11011等于27。

位運算 XOR

位運算XOR由符號(^)表示,當(dāng)然,也是直接對二進制形式進行運算。XOR不同于OR,當(dāng)只有一個數(shù)位存放的是1時,它才返回1。真值表如下:

第一個數(shù)字中的數(shù)位第二個數(shù)字中的數(shù)位結(jié)果
110
101
011
000

對25和3進行XOR運算,代碼如下:

var iResult = 25 ^ 3;
alert(iResult);	//輸出 "26"

25和3進行XOR運算的結(jié)果是26:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

可以看出,在兩個數(shù)字中,共有4個數(shù)位存放的是1,這些數(shù)位被傳遞給結(jié)果。二進制代碼11010等于26。

左移運算

左移運算由兩個小于號表示(<<)。它把數(shù)字中的所有數(shù)位向左移動指定的數(shù)量。例如,把數(shù)字2(等于二進制中的10)左移5位,結(jié)果為64(等于二進制中的 1000000):

var iOld = 2;		//等于二進制 10
var iNew = iOld << 5;	//等于二進制 1000000 十進制 64

注意:在左移數(shù)位時,數(shù)字右邊多出5個空位。左移運算用0填充這些空位,使結(jié)果成為完整的32位數(shù)字。

數(shù)字 2 進行左移運算

注意:左移運算保留數(shù)字的符號位。例如,如果把-2左移5位,得到的是-64,而不是64。“符號仍然存儲在第32位中嗎?”是的,不過這在ECMAScript后臺進行,開發(fā)者不能直接訪問第32個數(shù)位。即使輸出二進制字符串形式的負數(shù),顯示的也是負號形式(例如,-2將顯示-10。)

有符號右移運算

有符號右移運算符由兩個大于號表示(>>)。它把32位數(shù)字中的所有數(shù)位整體右移,同時保留該數(shù)的符號(正號或負號)。有符號右移運算符恰好與左移運算相反。例如,把64右移5位,將變?yōu)?:

var iOld = 64;		//等于二進制 1000000
var iNew = iOld >> 5;	//等于二進制 10 十進制 2

同樣,移動數(shù)位后會造成空位。這次,空位位于數(shù)字的左側(cè),但位于符號位之后。ECMAScript用符號位的值填充這些空位,創(chuàng)建完整的數(shù)字,如下圖所示:

數(shù)字 64 進行有符號右移運算

無符號右移運算

無符號右移運算符由三個大于號(>>>)表示,它將無符號32位數(shù)的所有數(shù)位整體右移。對于正數(shù),無符號右移運算的結(jié)果與有符號右移運算一樣。

用有符號右移運算中的例子,把64右移5位,將變?yōu)?:

var iOld = 64;		//等于二進制 1000000
var iNew = iOld >>> 5;	//等于二進制 10 十進制 2

對于負數(shù),情況就不同了。

無符號右移運算用0填充所有空位。對于正數(shù),這與有符號右移運算的操作一樣,而負數(shù)則被作為正數(shù)來處理。

由于無符號右移運算的結(jié)果是一個32位的正數(shù),所以負數(shù)的無符號右移運算得到的總是一個非常大的數(shù)字。例如,如果把-64右移5位,將得到134217726。如何得到這種結(jié)果的呢?

要實現(xiàn)這一點,需要把這個數(shù)字轉(zhuǎn)換成無符號的等價形式(盡管該數(shù)字本身還是有符號的),可以通過以下代碼獲得這種形式:

var iUnsigned64 = -64 >>> 0;

然后,用Number類型的toString()獲取它的真正的位表示,采用的基為2:

alert(iUnsigned64.toString(2));

這將生成11111111111111111111111111000000,即有符號整數(shù)-64的二進制補碼表示,不過它等于無符號整數(shù)4294967232。

出于這種原因,使用無符號右移運算符要小心。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號