您當前的位置:首頁 > 遊戲

Python二進位制表示和位操作

作者:由 老劉 發表于 遊戲時間:2016-12-16

我們都知道在計算機中所有的資訊最終都是以二進位制的0和1來表示,而有些演算法是透過操作bit位來進行運算的,這就需要我們瞭解Python中如何去表示二進位制,又如何是進行位運算的。

二進位制的表示

首先在Python中可以透過以“0b”或者“-0b”開頭的字串來表示二進位制,如下所示

print 0b101 # 輸出5

print 0b10 # 輸出2

print 0b111 # 輸出7

print -0b101 # 輸出-5

由此可知我們用二進位制表示的數字在列印之後會變成我們更為熟悉的十進位制數,更容易被人理解。

當我們需要看十進位制數字的二進位制表示時,可以使用bin函式

bin(5) # 輸出0b101

二進位制的位操作

首先一點需要明確的是所有的運算(包括位操作)在計算機內部都是透過補碼形式來進行運算的,關於補碼可以參考文章

原碼,反碼和補碼

,計算機內部運算示意圖如下:

image

在Python中提供瞭如下二進位制的位操作:

>> #右移

<< #左移

| #位或

& #位與

^ #位異或

~ #非

位運演算法則:

image

下面我們分別來看下:

左移

0b11 << 2 #輸出為12, 即0b1100

5 << 2 #輸出為20, 即0b10100

-2 << 2 #輸出為-8

5 << 64 #輸出為92233720368547758080L

以0b11為例,0b11的補碼就是0b11,所以左移就是將所有的0和1的位置進行左移,移位之後將空位補0。

負數的左移相對來說就比較複雜,以-2 << 2為例,-2的原碼是10000000000000000000000000000010(32位系統),其補碼為11111111111111111111111111111110,左移之後變為11111111111111111111111111111000,再轉化為原碼即10000000000000000000000000001000,也就是-8,也就是-2*(2**2)=-8

左移超過32位或者64位(根據系統的不同)自動轉化為long型別。

左移操作相當於乘以2**n,以5 << 3為例,相當於5

(2*

3),結果為40。

右移

0b11 >> 1 #輸出為1, 即0b1

5 >> 1 #輸出為2,即0b10

-8 >> 3 #輸出為-1

在Python中如果符號位為0,則右移後高位補0,如果符號位為1,則高位補1;

同樣需要先轉化為補碼再進行計算,以-8 >> 3為例,-8的原碼為10。。。01000,相應的補碼為11。。。11000,右移後變為1。。。1,相應的原碼為10。。。01,即-1。

右移操作相當於除以2**n,8 >> 3相當於8/(2**3)=1

0b110 | 0b101 #輸出7,即0b111

-0b001 | 0b101 #輸出-1

同樣是轉化為補碼後再進行或運算, 只要有一位有1就為1。

所以或運算常常用於mask技術中的開啟開關,即針對某一位把其置為1

比如將某個數字的第三位置為1,我們可以將mask設定為0b100,然後再或運算

mask = 0b100

0b110000 | mask #turn on bit 3

0b110 & 0b011 #輸出2,即0b010

與運算常常用於mask技術的關閉開關,即針對某一位把其置為0

mask = 0b10

0b111111 & mask #turn off bit 2

異或

0b111 ^ 0b111 #輸出0

0b100 ^ 0b111 #輸出3

異或常用於將所有的位反轉

0b1010 ^ 0b1111 #輸出5,即0b0101

~0b101 #輸出2,即0b010

~-3 #輸出2

非運算就是把0變1,1變0,唯一需要注意的是取非時符號位也會變換,比如-3,原碼是10。。。011,補碼是11。。。101,取非後變為00。。。010,由於符號位為0,所以對應的原碼即為其本身,即2。

二進位制工具

bitarray

關於bit有一個很有用的Packag叫做bitarray,其中bitarray物件可以幫助我們儲存0,1值或者Boolean值,並像list一樣進行操作。

from bitarray import bitarray

#初始化一個有10個bit位的陣列,初始值為0

a = bitarray(10)

#可以像操作list一樣操作bitarray物件

a[1:8] = 1

#bitarray還提供了一些特殊的方法,如:all()

#當bitarray中所有的元素都為1時,all()返回為True

if a。all():

print “all bits are True。”

關於bitarrary的說明詳見Github上的

bitarray專案

位運算的應用

常見的應用如判斷奇偶數 X & 0x1,變換符號位 ~X + 1,數字交換等,詳細可以看參考連結中的文章

下面筆者想就實際專案中的一個例子來說明位操作的應用。

下表是一個TS Package header的說明(TS流是流媒體行業常用的傳輸格式),我們看到為了減少不必要的浪費,包頭在定義域的時候都是按位進行定義的,那麼我們如果想要取相應的域的值,也就需要使用位操作了。

Packet Header(包頭)資訊說明

序號名稱bit數說明1sync_byte8bits同步位元組2transport_error_indicator1bit錯誤指示資訊(1:該包至少有1bits傳輸錯誤)3payload_unit_start_indicator1bit負載單元開始標誌(packet不滿188位元組時需填充)4transport_priority1bit傳輸優先順序標誌(1:優先順序高)5PID13bitsPacket ID號碼,唯一的號碼對應不同的包6transport_scrambling_control2bits加密標誌(00:未加密;其他表示已加密)7adaptation_field_control2bits附加區域控制8continuity_counter4bits包遞增計數器

我們以取PID值為例,當我們獲取到包頭的位元組串之後,我們需要如下幾個步驟:

需要取到第2個位元組,然後忽略第二個位元組的高三位(從表中可以看出高三位為其它資訊與PID無關);

將第二個位元組的後5位數字左移8位,這樣將其移到高位;

移位後與第3個位元組的數值相加得到PID的值。

要實現第一步,首先就要用到位操作中常用的mask技術,即透過將對應位為0的數值進行&操作

0b10110111 & 0b00011111 #將高位的3位進行mask關閉操作,使得其值被去除

要實現第二步,就需要用到左移操作,左移操作之後與第三個位元組的數值相加就是實際的PID值

完整程式碼實現如下:

def get_package_pid(package):

if package is None:

raise Exception(“get_package_pid param package is None。”)

return ((ord(package[1]) & 0x1f) << 8) + ord(package[2])

注:

1, ord()將byte串轉化為對應的數字從而進行位運算;

2, 0x1f是十六進位制表示,轉化為二進位制就是0b00011111。

參考連結:

你不知道的按位運算 - Just For Fun - SegmentFault

原碼, 反碼, 補碼 詳解 - ziqiu。zhang - 部落格園

wnduan/codecademy-py

循序漸進學Python之數值型別(一) - 51CTO。COM

標簽: 輸出  左移  補碼  bitarray  運算