python入門(五)—— 整數,小數(浮點數),複數

整數(int)存儲方式

關於數字類型的基本介紹,第三節已經說了大部分,這裏不再贅述。下面要說一點關於數字存儲的底層知識,這對於學過c/c++的同學很熟悉,如果不感興趣,可以跳過。

我們知道,計算機上所有的東西都是二進制,也就是一串010001101如此等等的數字。對於一個兩位的二進制,只能有四種排列:00,01,10,11,也就是說,只能用來表示四個不同的的東西,如果用來表示數字,只能表示0,1,2,3,當然如果你標新立異,非要用來表示5,6,7,8那也沒關係,總之,最多表示四個不同的東西。對於n位二進制來說,可以表示
2n 2^n
個不同的東西。計算機上,這個n是有限的,顯然,計算機上可以表示東西也是有限的。然而我們的數字是有無限多的,這就使得計算機不可能表示全部的整數,那麼計算機有什麼意義?好在,我們日常能夠使用的數字,相對來說都不會很大。比如,如果你要爲餐館寫一個統計天客人用餐信息的程序,一般也就100元左右一個人,一天的收入,肯定也不會超過一個億。所以,我們只要能夠表示我們常用的數字就行了。

現在的計算機一般都是64位,即CPU一次計算能夠處理64位二進制,也叫64bit(比特)。如果用64bit來表示整數,那麼可以表示整數個數爲
264=18446744073709551616 2^{64} = 18446744073709551616
同時,爲了能夠表示負數,這麼多數字中有一半是複數,還有一個是0,所以整數最大爲(263-1),最小爲-263

下面採用8bit整數來講述一下整數在計算機實際上是怎樣的。8bit一共256個數,其中127個正數加上0很多時候用來表示128個字符,稱之爲ASCII字符表,這對於c/c++同學很清楚,不知道的可以去百度一下。所以一般來說,把8bit叫做一個字節(Byte)。

正數就是一般的二進制排列

00000000   ---   0
00000001   ---   1
00000010   ---   2
00000011   ---   3
00000100   ---   4

01111111   ---   127

可以看到正數包括0,其二進制第一位是0,後面128個複數,二進制第一位就一直是1。複數是這樣實現的:

例如-126,寫出正126的二進制

01111110   ---   +126

將上面的二進制數字全部反過來,這一步叫求反碼

10000001   ---   +126的反碼

然後在這個反碼上加1,叫做+126補碼

10000010   ---   -126

就是-126的二進制表示

習題:試着寫出-1,-128的二進制表示

將負數做這樣的編碼的好處是,負數和正數的加法,可以直接使用二進制加法計算,以127+(-126)爲例

   01111111   ---   127
+  10000010   ---   -126
= 100000001   ---   ???
=  00000001   ---   1

不把第一位看作表示符號,而是普通的二進制數,直接相加,正常進位。但是相加結果超出了8bit,前面的位就直接捨去,這樣直接就得到了1。這樣,含有正負數的加減法的時候,計算機不用判斷一個數是不是負數,直接按照正數來做加減法就是,這顯然對於計算機運算速度是有好處的。

作業:按照二進制加法,127+1的二進制結果是什麼,它對應哪一個數字的二進制表示,-128±3呢?

可以看見,二進制的整數表示是有限的,如果表示整數的bit數是固定的,那麼會出現大數字不夠表示的情況,這叫做溢出。上面的這個作業,其實說明了溢出之後的結果,是錯誤的,但是結果還會在表示範圍之內。

python的整數存儲

對於python,他自己實現了變長的整數。所謂變長,就是當數字不大時,可能(具體時這樣的,還要看源代碼)使用32位的整數,當數字比較大,位數不夠用的時候,就加一段,還不夠,再加。所以理論上,python的整數可以是任意大的。不過當數字很大的時候,運算速度也會變得很慢。

位運算

數字的位運算,是從二進制的角度,來操作整數,這在python中並不常用,但是有的時候,也會有意想不到的用處:

  • 位與&:將數字寫成二進制表示,對每一位,做與運算:只有兩個都是1時,結果爲1,否者爲0。
  00010111   ---   23
& 00100010   ---   34
= 00000010   ---   2

所以,在交互式編程處,輸入23 & 34,得到:

>>> 23 & 34
2
  • 位或|:每一位,只要有一個是1,結果就是1,否者爲0。
>>> 23 | 34
55
  • 位非~:每一位,數字反過來,其實就是求反碼
>>> ~1
-2
  • 位異或^:每一位,兩個數字不同,就是1,否則爲0
>>> 23 ^ 34
53

後面幾個的二進制過程就不寫出來了,可以自己分析一下。異或有一個很有意思的用法,就是寫整數交換算法:

傳統的算法:

a = 100
b = 200
temp = a
a = b
b = temp

必須引入一箇中間變量temp記錄a的值,否者無法交換,使用異或則不用

a = a^b
b = a^b
a = a^b

經過上述三行代碼,就可以完成,其中用到異或的性質a^a=0, a^b=b^a,可以自己推導一下。

浮點數

和整數一樣,實數的顯然也是二進制表示的,那麼計算機能夠表示的實數也是有限的。計算機表示實數的方式叫做浮點數,它使用一個bit表示數字的符號s,使用t個bit表示二進制的小數點後t位,用剩下的bit表示指數e,最後表示的浮點數的結果是
x=(1)s(0.a1a2at)βe x = (-1)^s\cdot (0.a_1a_2\cdots a_t)\cdot\beta^e
二進制表示中,β=2,上面寫作小數的形式(0.a1a2at)(0.a_1a_2\cdots a_t),是二進制小數。

顯然,對於像圓周率pi,自然對數常數e這些無理數,實際上是不能夠使用浮點數表示。不過,從另外的角度看,我們實際工作中,對於實數的使用也並不是要求全部表示。比如,我們要計算某一個工程需要的水泥的量,我想應該還不至於精確到千克,大不了稍微多買一點,這個計算使用浮點數實際上已經足夠精確。

32bit的浮點數,常常稱爲單精度數,一般能夠精確到10-7,64bit的浮點數又叫雙精度數,一般能夠精確到10-16,這對於大多數情況下已經足夠精確了。

在現在通用的64bit計算機上,python是使用64bit浮點數的,也就是雙精度數,其最大的範圍是:10-308到10308,當然正負數都支持。這使得在python中,有的時候算出巨大的整數,還真的不能轉化爲浮點數。

類型轉換,可以這樣來做

>>> int(12.3)
12
>>> float(10**1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: int too large to convert to float

交互式下,type(12),就會輸出int,是整數的類型名,浮點數的類型名是float。類型轉換直接使用類型名作爲函數就好了。上述結果中轉化int(12.3)是轉化成功的,而後面的轉化失敗,就是因爲10**100太大了,超出了浮點數的表示範圍。

python並沒有拓展浮點數的使得其像整數一樣可以無限多,因爲這是不必要的。首先浮點數的拓展並不像整數那樣容易,並且浮點數常用在高性能計算中,運算速度更重要,而不是精度。對於精度問題,需要數學家和計算機學家很仔細的控制,在開啓一個超級計算程序之前,往往需要通過原理上的估算確保精度是足夠的,這需要更加深入的理論。不過日常生活,普通的工作,雙精度數已經沒有任何問題了。

數學函數庫

實數運算當然需要一些數學函數,下面是交互式命令行的一些演示

>>> import math        # 必須要先導入數學模塊,否則後面的東西都無法使用
>>> math.asin(0.5)     # 反正弦
0.5235987755982989
>>> math.log(2.3)      # 對數函數
0.8329091229351039
>>> math.exp(2.3)      # 指數函數
9.974182454814718
>>> dir(math)          # 查看math模塊中可用的東西
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']

其中下劃線的這些可以先不管,對於後面的一些函數,如果不知道是什麼意思,還記得前面說過的神器嗎。交互式下使用help函數就是了,比如

>>> help(math.hypot)
Help on built-in function hypot in module math:

hypot(x, y, /)
    Return the Euclidean distance, sqrt(x*x + y*y).

其中還值得一提的是math模塊裏面還有幾個常數

>>> math.pi
3.141592653589793
>>> math.e
2.718281828459045
>>> math.tau
6.283185307179586

關於實數,還有一個生成隨機數的模塊random

>>> import random
>>> random.random()
0.900955873808953
>>> random.random()
0.7794294909793891

你也可以用dir(random)看看其中有那些函數,隨機數可以寫好幾本專著了,這裏就不展開講。

複數

複數可以使用下面兩中方式創建

>>> 1+2j
(1+2j)
>>> complex(1,2)
(1+2j)
>>> complex(1)
(1+0j)

處理複數的模塊是cmath

>>> import cmath
>>> cmath.sqrt(1+2j)
(1.272019649514069+0.7861513777574233j)

其他的和運算都差不多,不一一列舉。關於複數的二進制表示,顯然實部和虛部分開,分別使用整數或者浮點數的表示法就好了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章