1. 程式人生 > >python中移位操作 問題

python中移位操作 問題

一、現象

Python 中執行左移操作(即將一個數的二進位制位整體向左移若干位,移位後在低位補零,高位溢位部分捨棄):

>>> 1000<<25
結果是:33554432000L
而在 C#、C++等語言中執行同樣的左移操作,結果卻迥然不同:

Console.WriteLine(1000<<25);

結果是:-805306368

再舉幾個 Python 例子:

>>> 1000L<<25 (注:L字尾代表Long資料型別)
33554432000L

>>> -1<<100
-1267650600228229401496703205376L

>>> 

1<<100
1267650600228229401496703205376L

而C#中執行同樣程式碼,結果有著巨大的差異:

Console.WriteLine(1000L << 25);

33554432000 LONG型別的左移結果是一致的)

Console.WriteLine(-1 << 100);

-16

Console.WriteLine(1 << 100);

16

Javascript的結果也是對的,把下面的程式碼儲存為html檔案:

<html>
<head>
<script type="text/javascript">
alert(2083363589<<5);
</script>
</head>
<body>
</body>
</html>

瀏覽器開啟這個html後得到對話方塊提示:-2051841888 ,也是正確的。

那麼,Python 的左移操作為何計算結果如此偏頗呢?

問題何在?

即使是 Python 2.5 乃至最新的 Python 3.1.1 都是這個結果

(只不過Python3執行 1000<<25 的結果是 33554432000 ,沒有加L字尾),

莫非這麼多年來沒人做左移操作嗎?

我們先來了解Python 怎麼定義的吧:

二、Python Doc 對左移的定義

A right shift by n bits is defined as division by pow(2,n). A left shift by n

 bits is defined as multiplication with pow(2,n);

for plain integers there is no overflow check so in that case the operation drops bits and flips the sign if the result is not less than pow(2,31) in absolute value. Negative shift counts raise aValueError exception.

(譯文:右移n位可以定義為除以pow(2,n),左移n位可以定義為乘以pow(2,n);對於普通整數是沒有溢位檢查的,因此若結果的絕對值不小於pow(2,31), 這個運算會截掉相應的位並且符號位也在移位處理之列. )

Python的 x<<y 相當於直接呼叫:

int(x * 2**y) 函式

還不要說負數的左移操作所遇到的問題了:

Shifting negative numbers doesn't have consistent interpretation between python and C.(譯文:負數的位移操作,python與C語言的解釋是不一致的。)

三、為什麼會這樣?

Python 創始人 Guido van Rossum ,在今年2月份的博文

中講述了當初設計 Python 整數型別時犯下的嚴重錯誤,以至於“在特定情況下,integer和long兩種整數實現會有語義上的細微不同”,並進一步導致:

“In addition, the int type, while normally considered signed, was treated as

an unsigned number by bitwise and shift operations and by conversions to/from octal and hexadecimal representations. Longs, on the other hand, were always considered signed. Therefore, some operations would produce a different result depending on whether an argument was represented as an int or a long.”

(譯文:int型別通常情況下是有符號數,在位操作、位移操作、和8進位制/16進位制互相轉換時則當做無符號數。而相對應的,long型別則總是有符號數因此,某些操作會因為引數是由int還是long型別表達而產生不同的結果。)

他舉例說:在32位運算中,1<<31(1左移31位)是一個32位的大負數,而1<<32結果為0。然而1L<<31(long型別的1左移31位)產生一個long型別整數,值為2**31,1L<<32的結果為2**32。

最開始,他通過讓運算結果超出儲存範圍時丟擲溢位異常(OverflowError)修正這一錯誤,

但很快有人抱怨這一點,於是他修正為:

I should have made integer operations that overflow promote their result to longs. This is the way that Python works today, but it took a long time to make this transition.

(譯文:我應該讓溢位的int整數操作結果升級為long型別。這也是今天Python採用的方式,可惜這個修正太晚了。)

但不管怎樣,位移操作的問題始終沒有被修正。

while True: x = 1 << 64 會導致記憶體洩漏;而 while True: 1L << 64 則不會。

四、怎麼辦?

不知道。

我們把左移操作放入C++中,讓Python呼叫。

五、背景介紹

左移運算

就是將一個數的二進位制位整體向左移若干位,移位後在低位補零,高位溢位部分捨棄。所以1<<2就是把整數1的二進位制補碼 00000000 00000000 00000000 00000001(Python的整型資料的位寬是32位,所以要補這麼長)整體左移2位,捨棄溢位的高位並在低位補零後得到結果00000000 00000000 00000000 00000100,正好是十進位制數4即22的補碼。實際上,將一個數左移幾位,就相當於將這個數乘以2的幾次冪。

型別長度

Python的整型資料的位寬是32位,8個位元組。int 最大值是2147483647 (sys.maxint),而long 長度僅受記憶體大小限制。

C/C++ 中,int 的長度 與 機器字長 相同,16位的編譯器上int長16位,32位的編譯器上int長32位。

鄭昀@玩聚SR 北京報道