有些時候二進位表示法非常好用,如要設定 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,所以也可以用這種方式解析二進位資料