1. 程式人生 > >Python 的整數與 Numpy 的資料溢位

Python 的整數與 Numpy 的資料溢位

某位 A 同學發了我一張截圖,問為何結果中出現了負數?

看了圖,我第一感覺就是資料溢位了。資料超出能表示的最大值,就會出現奇奇怪怪的結果。

然後,他繼續發了張圖,內容是 print(100000*208378),就是直接列印上圖的 E[0]*G[0],結果是 20837800000,這是個正確的結果。

所以新的問題是:如果說上圖的資料溢位了,為何直接相乘的數卻沒有溢位?

由於我一直忽視資料的表示規則(整型的上限是多少?),而且對 Numpy 瞭解不多,還錯看了圖中結果,誤以為每一個數據都是錯誤的,所以就解答不出來。

最後,經過學習群裡的一番討論,我才終於明白是怎麼回事,所以本文把相關知識點做個梳理。

在正式開始之前,先總結一下上圖會引出的話題:

  • Python 3 中整數的上限是多少?Python 2 呢?
  • Numpy 中整數的上限是多少?出現整數溢位該怎麼辦?

關於第一個問題,先看看 Python 2,它有兩種整數:

  • 一種是短整數,也即常說的整數,用 int 表示,有個內建函式 int()。其大小有限,可通過sys.maxint() 檢視(取決於平臺是 32 位還是 64 位)
  • 一種是長整數,即大小無限的整數,用 long 表示,有個內建函式 long()。寫法上是在數字後面加大寫字母 L 或小寫的 l,如 1000L

當一個整數超出短整數範圍時,它會自動採用長整數表示。舉例,列印 2**100 ,結果會在末尾加字母 L 表示它是長整數。

但是到了 Python 3,情況就不同了:它僅有一種內建的整數,表示為 int,形式上是 Python 2 的短整數,但實際上它能表示的範圍無限,行為上更像是長整數。無論多大的數,結尾都不需要字母 L 來作區分。

也就是說,Python 3 整合了兩種整數表示法,使用者不再需要自行區分,全交給底層按需處理。

理論上,Python 3 中的整數沒有上限(只要不超出記憶體空間)。這就解釋了前文中直接列印兩數相乘,為什麼結果會正確了。

PEP-237(Unifying Long Integers and Integers)中對這個轉變作了說明。它解釋這樣做的 目的:

這會給新的 Python 程式設計師(無論他們是否是程式設計新手)減少一項上手前要學的功課。

Python 在語言運用層遮蔽了很多瑣碎的活,比如記憶體分配,所以,我們在使用字串、列表或字典等物件時,根本不用操心。整數型別的轉變,也是出於這樣的便利目的。(壞處是犧牲了一些效率,在此就不談了)

回到前面的第二個話題:Numpy 中整數的上限是多少?

由於它是 C 語言實現,在整數表示上,用的是 C 語言的規則,也就是會區分整數和長整數。

有一種方式可檢視:

import numpy as np

a = np.arange(2)
type(a[0])

# 結果:numpy.int32

也就是說它預設的整數 int 是 32 位,表示範圍在 -2147483648 ~ 2147483647。

對照前文的截圖,裡面只有兩組數字相乘時沒有溢位:100007*4549、100012*13264,其它資料組都溢位了,所以出現奇怪的負數結果。

Numpy 支援的資料型別要比 Python 的多,相互間的區分界限很多樣:

截圖來源:https://www.runoob.com/numpy/numpy-dtype.html

要解決整數溢位問題,可以通過指定 dtype 的方式:

import numpy as np

q = [100000]
w = [500000]

# 一個溢位的例子:
a = np.array(q)
b = np.array(w)
print(a*b)  # 產生溢位,結果是個奇怪的數值

# 一個解決的例子:
c = np.array(q, dtype='int64')
d = np.array(w, dtype='int64')
print(c*d) # 沒有溢位:[50000000000]

好了,前面提出的問題就回答完了。來作個結尾吧:

  • Python 3 極大地簡化了整數的表示,效果可表述為:整數就只有一種整數(int),沒有其它型別的整數(long、int8、int64 之類的)
  • Numpy 中的整數型別對應於 C 語言的資料型別,每種“整數”有自己的區間,要解決資料溢位問題,需要指定更大的資料型別(dtype)

公眾號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關注哦。