位元運算
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}
>>>