JavaScript基礎(一)——位操作符

JavaScript基礎(一)——位操作符

  • 位操作符用於在最基本的層次上,即按內存(Memory)中表示數值的位來操作數值。ECMAScript中的所有數值都以IEEE-754 64格式存儲,但位操作符並不直接操作64位的值。而是先將64位的值轉換成32位的整數,然後執行操作,最後再結果轉換回64位。
  • 對於有符號的整數, 32位中的前31位用於表示整數的值第32位用於表示數值的符號: 0表示正數, 1表示負數。這個表示符號的位叫做符號位, 符號位的值決定了其他位數值的格式。 其中, 正數以純二進制格式存儲, 31位中的每一位都表示2的冪。第一位(叫做位0)表示2^0, 第二位表示2^1, 以此類推。沒有用到的位以0填充, 即忽略不計。例如, 數值18的二進制表示是00000000000000000000000000010010, 或者更簡潔的10010。這是5個有效位, 這5位本身就決定了實際的值。
1         0         0         1         0
(2^4*1) + (2^3*0) + (2^2*0) + (2^1*1) + (2^0*0)
16 + 0 + 0 + 2 + 0
18 
  • 負數同樣以二進制碼存儲, 但使用的格式是二進制補碼。計算一個數值的二進制補碼, 需要經過下列3個步驟:
    • 求這個數值絕對值的二進制碼(例如, 要求-18的二進制補碼, 先求18的二進制碼);
    • 求二進制反碼, 即將0替換爲1, 將1替換爲0;
    • 得到的二進制反碼加1。
  • 要根據這3個步驟求得-18的二進制碼, 首先就要求得18的二進制碼, 即:
0000 0000 0000 0000 0000 0000 0001 0010
  • 然後, 求其二進制反碼, 即0和1互換:
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。要注意的是, 在處理有符號整數時, 是不能訪問位31的。
  • ECMAScript會盡力向我們隱藏所有這些信息。換句話說, 在以二進制字符串形式輸出一個負數時, 我們看到的只是這個負數絕對值的二進制碼前面加上一個負號。如下面的例子所示:
var num = -18;
console.log(num.toString(2)); // "-10010"
  • 要把數值-18轉換成二進制字符串時, 得到的結果是"-10010"。這說明轉換過程理解了二進制補碼並將其以更合符邏輯的形式展示了出來。

    課外知識: 默認情況下, ECMAScript中的所有整數都是有符號整數。不過, 當然也存在無符號整數。對於無符號整數來說, 第32位不再表示符號, 因爲無符號整數只能是正數。而且, 無符號整數的值可以更大, 因爲多出的一位不再表示符號, 可以用來表示數值。

  • 在ECMAScript中, 當對數值應用位操作符時, 後臺會發生如下轉換過程: 64位的數值被轉換成32位數值, 然後執行位操作符, 最後再將32位的結果轉換回64位數值。這樣, 表面上看起來就好像是在操作32位數值, 就跟在其他語言中以類似方式執行二進制操作一樣。但這個轉換過程也導致了一個嚴重的副效應, 即在對特殊的NaNInfinity值應用位操作符時, 這兩個值都會被當成0來處理。

  • 如果對非數值應用操作符, 會先使用Number()函數將該值轉換爲一個數值(自動完成), 然後再應用位操作。得到的結果將是一個數值。

按位非(NOT)

  • 按位非操作符由一個波浪線(~)表示, 執行按位非的結果就是返回數值的反碼。按位非是ECMAScript操作符中少數幾個與二進制計算有關的操作符之一。下面看一個例子:
var num1 = 25; // 二進制 0000 0000 0000 0000 0000 0000 0001 1001
var num2 = -num1; //二進制 1111 1111 1111 1111 1111 1111 1110 0110
console.log(num2); //-26 
  • 這裏, 對25執行按位非操作, 結果得到了-26。這也驗證了按位非操作的本質: 操作數的負值減1。因此, 下面的代碼也能得到相同的結果:
var num1 = 25;
var num2 = -num1 - 1;
console.log(num2); // -26
  • 雖然以上代碼也能返回同樣的結果, 但由於按位非是在數值表示的是最底層執行操作, 因此速度更快。

按位與(AND)

  • 按位與操作符由一個和號字符(&)表示,它有兩個操作符數。從本質上講,按位與操作就是將兩個數值得每一位對齊,然後根據下表中的規則,對相同位置上的兩個數執行AND操作:
第一個數值的位 第二個數值的位 結果
1 1 1
1 0 0
0 1 0
0 0 0

- 簡而言之,按位與操作只在兩個數值的對應位都是1時才返回1,任何一位是0,結果都是0。

var result = 25 & 3;
console.log(result); //1
  • 可見,對25和3執行按位與操作的結果爲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的二進制碼對應上只有一位同時是1,而其他位的結果自然都是0,因此最終結果等於1。

按位或(OR)

  • 按位或操作符由一個豎線符號(|)表示,同樣也有兩個操作數。按位或操作遵循下面這個真值表。
第一個數值的位 第二個數值的位 結果
1 1 1
1 0 1
0 1 1
0 0 0

- 由此可見,按位或操作在有一個位是1的情況下就返回1,而只有在兩個都爲0的情況下才返回0。如果在前面按位與的例子中對25和3執行按位或操作,則代碼如下所示:

var result = 25 | 3;
console.log(result); //27
  • 25與3按位或的結果是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
  • 這兩個數值的都包含4個1,因此可以把每個1直接放到結果中。二進制碼11011轉換爲十進制爲27。

按位異或(XOR)

  • 按位異或操作符由一個插入符號(^)表示,也有兩個操作數。以下是按位異或的真值表。
第一個數值的位 第二個數值的位 結果
1 1 0
1 0 1
0 1 1
0 0 0

- 按位異或與按位或的不同之處在於,這個操作在兩個數值對應位上只有一個1時才返回1,如果對應的兩位都是1或者都是0,則返回0。
- 對25和3執行按位異或操作的代碼如下所示:

var result = 25 ^ 3;
console.log(result);
  • 25與3按位異或的結果是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
  • 這兩個數值都包含4個1,但第一位上則都是1,因此結果的第一位變成了0。而其他位上的1在另外一個數值中都沒有對應的1,可以直接放到結果中。二進制碼11010等於十進制26(注意這個結果比執行按位或時小1)。

左移

  • 左移操作符是由兩個小於號(<<)表示,這個操作符會將數值的所有位向左移動指定的位數。例如,如果將數值2(二進制碼爲10 )向左移動5位,結果就是64(二進制碼爲1000000),代碼如下所示:
var oldValue = 2; // 等於二進制的10
var newValue = oldValue << 5; // 等於二進制的1000000,十進制的64
  • 注意,在向左移位後,原數值的右側多出了5個空位。左移操作會以0來填充這些空位,以便得到的結果是一個完整的32位二進制數。
數值2
0000 0000 0000 0000 0000 0000 0000 0010
|
隱藏的符號位
將數值2向左移5位(得到64)
0000 0000 0000 0000 0000 0000 0010 00000填充

注意,左移不會影響操作數的符號位。換句話說,如果將-2向左移動5位,結果將是-64,而非64。

-2 = 1111 1111 1111 1111 1111 1111 1111 1110
向左移動5位
1111 1111 1111 1111 1111 1111 1110 0000
數值爲-64

有符號的右移

  • 有符號的右移操作由兩個大於號(>>)表示,這個操作符會將數值向右移動,但保留符號位(即正負號標記)。有符號的右移操作與左移操作恰好相反,即如果將64向右移動5位,結果將變回2:
var oldValue = 64; // 等於二進制的1000000
var newValue = oldvalue >> 5; //等於二進制的10,也就是十進制的2
  • 同樣,在移位過程中,原數值中也會出現空位。只不過這次的空位出現在原數值的左側、符號位的右側。而此時ECMAScript會用符號位的值來填充所有空位,以便得到一個完整的值。
數值64
0000 0000 0000 0000 0000 0000 0010 0000
|
隱藏的符號位
0000 0000 0000 0000 0000 0000 0000 0010
 |    |         0填充(符號位的值)

無符號右移

  • 無符號右移操作符由3個大於號(>>>)表示,這個操作符會將數值的所有32位都像右移動。對正數來書,無符號右移的結果與有符號右移相同。仍以前面有符號右移的代碼爲例,如果將64無符號右移5位,結果仍然還是2:
var oldValue = 64; // 等於二進制的1000000
var newValue = oldValue >>> 5; // 等於二進制的10,即十進制的2
  • 但是對於負數來說,情況就不一樣了。首先,無符號右移是以0來填充空位,而不是像有符號右移那樣以符號位的值來填充空位。所以,對正數的無符號右移與有符號右移結果相同,但對負數的結果就不一樣了。其次,無符號右移操作符會把負數的二進制碼當成正數的二進制碼。而且,由於負數以其絕對值的二進制補碼形式表示,因此就會導致無符號右移後的結果非常之大,如下面的例子所示:
var oldValue = -64; // 等於二進制的1111 1111 1111 1111 1111 1111 1111 1110 0000
var newValue = oldValue >>> 5; 
console.log(newValue); // 等於十進制的134217726
  • 這裏,當對-64執行無符號右移5位的操作後,得到的結果是134217726。之所以結果如此之大,是因爲-64的二進制碼爲1111 1111 1111 1111 1111 1111 1110 0000,而且無符號右移操作會把這個二進制碼當成正數的二進制碼,換算成十進制就是4294967232。如果把這個值右移5位,結果就變成了0000 0111 1111 1111 1111 1111 1111 1110,也就是十進制的134217726

JackDan Thinking

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章