有些時候二進位表示法非常好用,如要設定 User 權限是否有 新增、修改、刪除、查詢權限,若每個權限都用二進位的 1 個 bit 表示,0b1111 就表示 4 種權限都有,而 0b1010 則表示只有 新增 與 刪除 權限,以此類推,但這種二進位表示法,該如何在 ECMAScript 使用呢 ?
Version
macOS Catalina 10.15.1
VS Code 1.40.1
Quokka 1.0.261
ECMAScript 2015
Bitwise Operator
&: 對 bit 做 AND 運算|: 對 bit 做 OR 運算~: 若 bit 做 NOT 運算^: 對 bit 做 XOR 運算<<: 對 bit 做 SHIFT 運算,如1 << 2,則相當於1向左推兩位,成為0b100

所有邏輯的 truth table 如上圖所示。
Mask
ECMAScript 並沒有控制某一個 bit 的語法,若要對某一 bit 做讀寫,主要是靠 mask。
Mask
以二進位表示對某一 bit 的遮罩,如若要對第 2 bit 做處理,其 mask 則為0b0100
(最右方從 bit 0 開始,非 bit 1)。
Example
設定 user 新增、修改、刪除、查詢 權限,以二進位 4 bit 表示 :
新增: 以第 3 bit 表示,如0b1000修改: 以第 2 bit 表示,如0b0100刪除: 以第 1 bit 表示,如0b0010查詢: 以第 0 bit 表示,如0b0001
若 user 只有 新增 與 查詢 權限,則為 0b1001,以此類推。
Read
判斷 user 是否
有修改權限 ?
白話 : 判斷第 2 bit 是否為 1 ?
let data = 0b0101
let fn = flag => (flag & 0b0100) ? 'Bit 2 is enable' : 'Bit 2 is disable';
fn(data) // ?
(flag & 0b0100) 之後,只有第 2 bit 被 mask 過濾出來,若該 bit 為 1,則過濾後為 0b0100,基於 truthy value,所以 (flag & 0b0100) 為 true。

let data = 0b0101
let fn = flag => (flag & (1 << 2)) ? 'Bit 2 is enable' : 'Bit 2 is disable';
fn(data) // ?
也可使用 (1 << 2) 動態產生 mask,再與 flag 做 mask。

無論使用 mask 或 shift operator 寫法都必須了解,實務上兩種都會看到
判斷 user 是否
無修改權限 ?
白話 : 判斷第 2 bit 是否為 0 ?
let data = 0b0001
let fn = flag => (flag & 0b0100) ? 'Bit 2 is enable' : 'Bit 2 is disable';
fn(data) // ?
(flag & 0b0100) 之後,只有第 2 bit 被 mask 過濾出來,若該 bit 為 0,則過濾後為 0b0000,將 0 做 ! not 之後為 1,基於 truthy value,所以 !(flag & 0b0001) 為 true。

let data = 0b0001
let fn = flag => (flag & (1 << 2)) ? 'Bit 2 is enable' : 'Bit 2 is disable';
fn(data) // ?
也可使用 (1 << 2) 動態產生 mask,再與 flag 做 mask。

Write
讓 user
有修改權限
白話 : 讓第 2 bit 為 1,且其他 bit 不能被修改
let data = 0b0001
let fn = flag => (flag |= 0b0100).toString(2).padStart(4, '0')
fn(data) // ?

根據真值表得知,任何值 OR 1,其結果必為 1;任何值 OR 0,其結果不變。
因此 flag 只要與 mask 做 OR 運算,該 bit 一定為 1,其餘 bit 不變。
所以 flag = flag | mask,再簡化成 flag |= mask。

let data = 0b0001
let fn = flag => (flag |= (1 << 2)).toString(2).padStart(4, '0')
fn(data) // ?
若沒用 mask,也可使用 (1 << 2) 動態產生 mask,再與 flag 做 mask。

讓 user
無修改權限
白話 : 讓第 2 bit 為 0,且其他 bit 不能被修改
let data = 0b0101
let fn = flag => (flag &= ~(0b0100)).toString(2).padStart(4, '0')
fn(data) // ?

根據真值表得知,任何值 AND 0,其結果必為 0;任何值 AND 1,其結果不變。
因此 flag 只要與 mask 做 AND 運算,該 bit 一定為 1,其餘 bit 不變。
所以 flag = flag &= ~mask,再簡化成 flag &= ~mask。

let data = 0b0101
let fn = flag => (flag &= ~(1 << 2)).toString(2).padStart(4, '0')
fn(data) // ?
若沒用 mask,也可使用 (1 << 2) 動態產生 mask,再與 flag 做 mask。

若 user
有修改權限,則改成無修改權限,若無修改權限,則改成有修改權限
白話 : 若第 2 bit 為 0,則 toggle 為 1,若為 1,則 toggle 為 0
let data = 0b0101
let fn = flag => (flag ^= 0b0100).toString(2).padStart(4, '0')
fn(data) // ?

0 ^ 1 為 0,1 ^ 1 為 0,也就是任何數與 1 XOR,剛好都 toggle 成另外一個值。
0 ^ 0 為0,1 ^ 0 為 0,也就是任何數與 0 XOR,結果都不變。
因此 flag 只要與 mask 做 XOR 運算,該 bit 一定為 toggle,其餘 bit 不變。

let data = 0b0101
let fn = flag => (flag ^= (1 << 2)).toString(2).padStart(4, '0')
fn(data) // ?
若沒用 mask,也可使用 (1 << 2) 動態產生 mask,再與 flag 做 mask。

Conclusion
- 本文雖然使用 ECMAScript 為範例,但 bitwise operator 事實上來自於 C 語言,且眾多語言都有支援
- 無論使用 mask 或 shift operator 寫法都必須了解,實務上兩種都會看到
- 這種寫法在 C 語言與 Verilog 經常使用,因為 IC 暫存器為了省空間,都是以二進位方式儲存,因此 firmware 必須使用 bitwise operator 去解析暫存器的資料;在高階語言則較少使用,但因為 ECMAScript 也提供 bitwise operator,所以也可以用這種方式解析二進位資料