iT邦幫忙

2021 iThome 鐵人賽

DAY 2
0
Modern Web

舌尖上的JS系列 第 2

D2 - 先生 幫您帶位元運算子

前言

JavaScript 運算子大概可以分為幾大類:算數運算子、邏輯運算子、賦值運算子、比較運算子和位元運算子...

大部分的運算子在閱讀規範後都能快速上手,但其中的位元運算子在理解上相對沒那麼直覺,但 只要掌握了二進制表達方式,這就只是塊小小小雞蛋糕了

所以 先從了解 二進制binary 開始吧! /images/emoticon/emoticon42.gif

二進制表達方式

在數字系統裡,以進位制表達的方式有 2、8、10、16 進位等,而一般日常熟悉的數字都是十進制,也就是以 10 作為進位基數的表達方式。

二進制 就是以 2 為底數的記數方式,以10 做數字表示,目前計算機就是以二進制的方式來儲存資料,1 代表open 0代表 off, 而每個數字就稱為一個 位元bit

下圖是二進制與十進制數字的表示比較

JS 中的位元運算子有哪些?

這次要介紹 6 種位元運算子 bitwise operator :

& Bitwise AND
| Bitwise OR
~ Bitwise NOT
^ Bitwise XOR
<< Left Shift
>> Sign-Propagating Right Shift

And & 位元運算子

是不是很眼熟? 這個符號 & 在邏輯運算子中也有!
邏輯運算子中的 And && 代表兩個條件都符合才是 true, 其一不符合就是 false

true  && true  === true
false && false === false
false && true  === false

而位元運算子的 & 也是同樣邏輯, 先將數字轉換為二進制後,把握 0 代表 false, 1 代表 true 的原則,再來就是一樣的邏輯判斷。

所以當 1 和 0 比對,等同比較 true && false,結果是 false 0
那 1 和 1 呢? true && true 比對的結果當然就是 true 1

小試身手 >> 3 & 2 結果是多少?

//先將 3 和 2 轉為二進制表達
//每個位元做 1 , 0 比較

   011  -> 3
&  010  -> 2
--------
   010  -> 2
   
 第一位: 1 & 0 === 0
 第二位: 1 & 1 === 1
 第三位: 0 & 0 === 0

再來一題 >> 5 & 7

   101  -> 5
&  111  -> 7
--------
   101  -> 5

判斷奇數偶數

由上面的舉例發現, 在二進制表示法中,奇數的第一位數都是 1,偶數的第一位都是 0,利用此特性就可以輕鬆篩選出奇數偶數

//重點比對第一位, 所以配上 & 1 判斷第一位是否為 1 或 0

10 & 1 ->0000
9 & 1 -> 0001
8 & 1 -> 0000
7 & 1 -> 001
6 & 1 -> 000
5 & 1 -> 001
4 & 1 -> 000
3 & 1 -> 001
2 & 1 -> 000
1 & 1 -> 001

//奇數 & 1 結果都是 1
//偶數 & 1 結果都是 0
//實作判斷奇數偶數 function

function isOdd(number){
    return (number & 1)===1;
}

function isEven(number){
    return (number & 1)===0;
}

isOdd(3) === true
isEven(6) === true

OR | 位元運算子

| 運算子只是將比對規則改為 OR 判斷,有 1 就 return 1 否則 return 0

1 true  || 1 true  === 1 true
0 false || 0 false === 0 false
0 false || 1 true  === 1 true

剛剛 3 & 2 === 2 改為 3 | 2 ?

  011  -> 3
| 010  -> 2
------
  011 -> 3
  
 第一位: 1 | 0 === 1
 第二位: 1 | 1 === 1
 第三位: 0 | 0 === 0
段落重點:
  • & 運算子: 1 & 1 return 1, 而0 & 1 return 0
  • | 運算子: 1 | 1 return 1, 而0 | 1 return 1

XOR ^ 位元運算子

^ XOR 運算子,全名 Exclusive Or,與一般的邏輯不同,^ 是當數字兩兩相同是 false 0 ,不同則是true 1

一樣用 3 ^ 2 試看看吧

  011  -> 3
^ 010  -> 2
------
  001  -> 1
  
 第一位: 1 ^ 0 === 1
 第二位: 1 ^ 1 === 0
 第三位: 0 ^ 0 === 0

<< Left Shift Operator 和 >> Right Shift Operator

這兩個運算子用來移動數字往符號指向的方向,<< 代表位元向左移, 空出來的位元補上0>> 代表位元向右移。

NOT ~ 位元運算子

~ NOT 運算子使用的是 補數 implement 的概念,數字被轉換為 32 位元的二進制表達,將 0 轉為11 轉為 0

修但幾咧 補數是啥東東

看到這個名詞時 google 看了很多文章,wiki 的解釋點了不下 10 次

正數和 0 的二補數就是該數字本身,負數的二補數則是將其對應正數按位元取反再加 1 - wikipedia

一直無法透徹理解為什麼要這樣設計,直到我看到這個 youtube 影片

Twos complement: Negative numbers in binary 推推! 原來是這樣啊

你有想過以二進制怎麼表示負數嗎?

實際上電腦會分配一個符號位 sign bit,來作為正負號表示, 0 表示正數 1表示負數

第一種最直覺的負數表示為 原碼 sign-and-magnitude
但使用這種表達方式會發現一個問題
電腦進行 -5 + 5 的運算時,結果會是 1101 + 0101 = 10010 等於 2 ?
安堆~~~~

//原碼 sign-and-magnitude

sign
 bit  4  2  1
 --------------
  1   1  1  1  // -7
  1   1  1  0  // -6
  1   1  0  1  // -5
  1   1  0  0  // -4
  1   0  1  1  // -3
  1   0  1  0  // -2
  1   0  0  1  // -1
  1   0  0  0  // -0
  0   0  0  0  //  0
  0   0  0  1  //  1
  0   0  1  0  //  2
  0   0  1  1  //  3
  0   1  0  0  //  4
  0   1  0  1  //  5
  0   1  1  0  //  6
  0   1  1  1  //  7

看來上面那種負數表示方法不太適用於電腦運算
那有另一種表示方法稱為 一補數(ones' complement)

在一補數表示時要做位元轉換,負數是將 1 0 互換,正數不變動。
所以 -5 是從 0101 位元轉換為 1010 表示

那這種方法有解決加總的結果嗎?

再試試 -5 + 5 , 1010 + 0101 = 10000 等於 -0
恩 看來好一點,但這個負號不覺得怪怪的嗎 -0

再另外試試 5 + (-3)
0101 + 1100 = 100001 因為只取4位數先不看最左邊的 1,加總起來的數字變為 1
不行啊 數字還是不對

//一補數 ones' complement

sign
 bit  4  2  1
 --------------
  1   0  0  0  // -7
  1   0  0  1  // -6
  1   0  1  0  // -5
  1   0  1  1  // -4
  1   1  0  0  // -3
  1   1  0  1  // -2
  1   1  1  0  // -1
  1   0  0  0  // -0
  0   0  0  0  //  0
  0   0  0  1  //  1
  0   0  1  0  //  2
  0   0  1  1  //  3
  0   1  0  0  //  4
  0   1  0  1  //  5
  0   1  1  0  //  6
  0   1  1  1  //  7

在一補數的正負數加總中可以發現和正確的答案都只差 1
所以 就產生了 一補數加上 1 的方式 二補數(two's complement)

再試一次剛剛的加總 -5 + 5
1011 + 0101 = 10000 一樣不看最左邊的數字,很好 等於0

5 + (-3) 呢?
0101 + 1101 = 10010
耶 是我們要的 2

//二補數 two's complement

sign
 bit  4  2  1
 --------------
  1   0  0  1  // -7 
  1   0  1  0  // -6
  1   0  1  1  // -5
  1   1  0  0  // -4
  1   1  0  1  // -3
  1   1  1  0  // -2
  1   1  1  1  // -1
  0   0  0  0  //  0
  0   0  0  1  //  1
  0   0  1  0  //  2
  0   0  1  1  //  3
  0   1  0  0  //  4
  0   1  0  1  //  5
  0   1  1  0  //  6
  0   1  1  1  //  7

其實二補數的負數算法也很簡單,只要將最左邊的數字帶上負號做加總就跟 原碼 sign-and-magnitude 算法一樣

1001 -> -8+1 = -7
1010 -> -8+2 = -6

二補數很好的解決了加總的問題,也不存在奇怪的 -0
現在再看看 wiki 說的

正數和 0 的二補數就是該數字本身。負數的二補數則是將其對應正數按位元取反再加 1

是不是就理解了呢?

透過補數進行位元 NOT 運算

看完上面了解補數的運作後,~ NOT 就是對每個位元進行補數轉換

 sign
 bit  4  2  1                ~ NOT 
 --------------------------------------- 
  1   0  0  1  // -7  | ~-7  0110  // 6
  1   0  1  0  // -6  | ~-6  0101  // 5
  1   0  1  1  // -5  | ~-5  0100  // 4
  1   1  0  0  // -4  | ~-4  0011  // 3
  1   1  0  1  // -3  | ~-3  0010  // 2
  1   1  1  0  // -2  | ~-2  0001  // 1
  1   1  1  1  // -1  | ~-1  0000  // 0
  0   0  0  0  //  0  | ~0   1111  // -1
  0   0  0  1  //  1  | ~1   1110  // -2
  0   0  1  0  //  2  | ~2   1101  // -3
  0   0  1  1  //  3  | ~3   1100  // -4
  0   1  0  0  //  4  | ~4   1011  // -5
  0   1  0  1  //  5  | ~5   1010  // -6
  0   1  1  0  //  6  | ~6   1001  // -7
  0   1  1  1  //  7  | ~7   1000  // -8

如果要在腦中做補位轉來轉去太困難,其實也可以直接套用這個公式 -(x + 1),就可以知道 ~下的數字是多少了!
做做下面幾題練練看吧

~16
~56
~63

 sign
 bit  
 64  32  16  8  4  2  1              
 ------------------------------------ 
  0   0   1  0  0  0  0   // 16
  1   1   0  1  1  1  1   // ~16 -> -64+32+8+4+2+1 = -17
  0   1   1  1  0  0  0   // 56
  1   0   0  0  1  1  1   // ~56 -> -54+4+2+1 = -57
  0   1   1  1  1  1  1   // 63
  1   0   0  0  0  0  0   // ~63 -> -64

double tilde ~~

這裡教個小技巧,你知道在JS中 ~~ 3.2 是什麼意思嗎
剛剛學到 ~ 是補位,那兩個 ~~ 是補位再補位,等於自己本身,那 這個有什麼意義嗎?

適用在對非整數數字無條件捨去! 因為 JavaScript 位元運算子中都是將數字轉為 32 位元的整數呈現,所以可以利用這個原則得到取整數這個效果

//對小數點 2.5 進行兩次 NOT 位元運算

~2.5 === -3
~(-3)=== 2

以上
一直很想好好整理的位元運算子完成了呢 /images/emoticon/emoticon42.gif
其中補數觀念還不太理解的朋友真的推薦去看看這支影片 Twos complement: Negative numbers in binary

對了 在 Mac 的計算機也可以進行位元運算唷~~~
在計算機上按快捷鍵 cmd + 3 就能快速切換成程式設計計算機
來玩玩看吧!


上一篇
來一道色香味俱全的 JavaScript 吧
下一篇
D3 - 今天點個 String Methods 套餐
系列文
舌尖上的JS30

尚未有邦友留言

立即登入留言