位元運算

April 12, 2022

在數位設計上有 AND、OR、NOT、XOR 與補數運算,Python 提供對應的位元運算子(bitwise Operator),分別是 &(AND)、|(OR)、^(XOR)與 ~(補數)。

基本位元運算

如果不會基本位元運算,可以從以下範例瞭解各個位元運算結果:

print('AND運算:')
print('0 AND 0 {:5d}'.format(0 & 0))
print('0 AND 1 {:5d}'.format(0 & 1))
print('1 AND 0 {:5d}'.format(1 & 0))
print('1 AND 1 {:5d}'.format(1 & 1))

print('\nOR運算:')
print('0 OR 0 {:6d}'.format(0 | 0))
print('0 OR 1 {:6d}'.format(0 | 1))
print('1 OR 0 {:6d}'.format(1 | 0))
print('1 OR 1 {:6d}'.format(1 | 1))

print('\nXOR運算:')
print('0 XOR 0 {:5d}'.format(0 ^ 0))
print('0 XOR 1 {:5d}'.format(0 ^ 1))
print('1 XOR 0 {:5d}'.format(1 ^ 0))
print('1 XOR 1 {:5d}'.format(1 ^ 1))

執行結果就是各個位元運算的結果:

AND運算:
0 AND 0     0
0 AND 1     0
1 AND 0     0
1 AND 1     1

OR運算:
0 OR 0      0
0 OR 1      1
1 OR 0      1
1 OR 1      1

XOR運算:
0 XOR 0     0
0 XOR 1     1
1 XOR 0     1
1 XOR 1     0

位元運算是逐位元運算,例如 10010001 與 01000001 作 AND 運算,是一個一個位元對應運算,答案就是 00000001。補數運算是將所有位元 0 變 1,1 變 0。例如 00000001 經補數運算就會變為 11111110:

>>> 0b10010001 & 0b01000001
1
>>> number1 = 0b0011
>>> number1
3
>>> ~number1
-4
>>> number2 = 0b1111
>>> number2
15
>>> ~number2
-16
>>>

number1 的 0011 經補數運算就變成 1100,這個數在電腦中以二補數 來表示就是 -4。

Python 的整數是有號整數,使用二進位實字寫法表示一個整數時,例如用 0b1111 表示 15,實際上 1111 更左邊的位元會是 0,經過補數運算後,1111 的部份會變成 0000,而更左邊的位元變成 1,整個值用二補數來表示就是 -16。

在位元運算上,Python 還有左移(<<)與右移(>>)兩個運算子,左移運算子會將所有位元往左移指定位數,左邊被擠出去的位元會被丟棄,而右邊補上 0;右移運算則是相反,會將所有位元往右移指定位數,右邊被擠出去的位元會被丟棄,至於最左邊補上原來的位元,如果左邊原來是 0 就補 0,1 就補 1。

使用左移運算來作簡單的2次方運算示範:

number = 1
print('2 的 0 次方: ', number);
print('2 的 1 次方: ', number << 1)
print('2 的 2 次方: ', number << 2)
print('2 的 3 次方: ', number << 3) 

執行結果如下:

2 的 0 次方:  1
2 的 1 次方:  2
2 的 2 次方:  4
2 的 3 次方:  8

實際來左移看看,就知道為何可以如此作次方運算了:

00000001	→ 1 
00000010	→ 2 
00000100	→ 4 
00001000	→ 8

Python 不支援 >>>,因為 >>> 是無號右移(unsigned right shift),不管值本來最左邊是什麼位元,位移後最左邊位元直接補 0,這意謂著代表某整數值的位元數量是有限的,然而,Python 的整數是 int 實例,支援大整數,整數沒有上限的概念(當然,有實體記憶體的物理限制),也就是相當於位元組數量無限,也就不支援 >>> 了。

應用於 set

在〈集合〉看過,&|^ 不只能用在數值型態上,還可以應用在 set 型態,這是 Python 最有趣也最實用的特性之一。例如,若想比較兩個使用者群組的狀態,可以如下:

import sys

admins = {'Justin', 'caterpillar'}
users = set(sys.argv[1:])
print('站長:{}'.format(admins & users))
print('非站長:{}'.format(users - admins))
print('全部使用者:{}'.format(admins | users))
print('身份不重複使用者:{}'.format(admins ^ users))
print('站長群包括使用者群?{}'.format(admins > users))
print('使用者群包括站長群?{}'.format(admins < users))

使用在 set 型態時,& 可用來進行交集,| 可用來做聯集,^ 可用來做互斥,除此之外,上面的程式中也看到了,- 可用來做減集,><(以及 >=<===)可用來測試兩集合的包括關係。一個測試範例如下:

>python groups.py Justin Monica momor Irene hamimi
站長:{'Justin'}
非站長:{'Monica', 'momor', 'Irene', 'hamimi'}
全部使用者:{'caterpillar', 'Monica', 'momor', 'Irene', 'Justin', 'hamimi'}
身份不重複使用者:{'caterpillar', 'momor', 'Irene', 'hamimi', 'Monica'}
站長群包括使用者群?False
使用者群包括站長群?False

在上面的範例中,看到了 sys.argv[1:] 這個程式碼,這是 Python 的切片(Slicing)運算,意思是將 sys.argv 索引 1 開始,至 list 尾端的全部元素,切出成為新的 list,這是為了取得使用者輸入的命令列引數(因為 sys.argv[0] 是 .py 檔案名稱),之後再交給 set 函式轉換為 set 型態。稍後將會認識更多切片運算的方式。

應用於 dict

在〈字典〉看過,從 Python 3.9 開始,| 可以應用在 dict 型態,作用是合併 dict,傳回新 dict,若作為運算元的 dict 有重複鍵值,傳回的 dict 會使用 | 右運算元的鍵值,例如:

>>> d1 = {'a': 10, 'b': 20}
>>> d2 = {'b': 30, 'c': 40}
>>> r = d1 | d2
>>> r
{'a': 10, 'b': 30, 'c': 40}
>>>

分享到 LinkedIn 分享到 Facebook 分享到 Twitter