2019年10月21日 星期一

Python 學習筆記 : 數學模組 math 與 cmath 測試

在學習 Python 第三方套件 Numpy 之前我想先對 Pyhon 內建的 math 模組進行較完整的測試, 因為對於非向量運算來說, 使用 math 模組提供的函數就綽綽有餘了, 而且運算效能比較好. 不過 math 模組只能處理實數運算, Python 提供另一個內建模組 cmath 專門用來處理複數運算.

本系列測試文章索引參考 :

Python 學習筆記索引

math 與 cmath 模組的說明文件參考 :

https://docs.python.org/3/library/math.html
https://docs.python.org/3/library/cmath.html


1. 匯入 math 模組 : 

使用 math 模組須先匯入 :

import math

這樣呼叫 math 的方法時必須前綴模組名稱 math, 例如 math.sin(1). 也可以取一個較短的別名 (alias) :

import math as m 

這樣就可以用別名 m.sin() 來呼叫函數了.

用 dir() 檢視 math 模組的成員 (屬性與方法) :

>>> import math           #math 模組只能處理實數
>>> dir(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']

用 dir() 檢視 cmath 模組的成員 (屬性與方法) :

>>> import cmath         #cmath 模組可處理複數
>>> dir(cmath) 
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'cos', 'cosh', 'e', 'exp', 'inf', 'infj', 'isclose', 'isfinite', 'isinf', 'isnan', 'log', 'log10', 'nan', 'nanj', 'phase', 'pi', 'polar', 'rect', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau']

可見大部分函數均與 math 模組一樣, 常數 e, pi, tau, nan, inf 都是相同的實數, 但 cmath 多了虛數的 infj 與 nanj.


2. math 模組中的數學常數 : 

math 模組中定義了如下五個常數 (屬性) :


 math 常數 說明
 e 自然指數=2.718281828459045
 pi 圓周率=3.141592653589793
 tau 圓周率的兩倍=2*pi
 nan 非數值=float('nan')  
 inf 正無限大之浮點數=float('inf')


其中 nan 與 inf 也可以用 float('nan') 與 float('inf') 產生, 並有對應的方法 isnan() 與 isinf() 用來檢測傳入參數是否為 nan 與 inf.

例如 :

>>> import math   
>>> math.e                                   #自然指數
2.718281828459045
>>> math.pi                                 #圓周率
3.141592653589793
>>> math.nan                              #非數值
nan
>>> type(math.nan)                    #nan 為 float 型態
<class 'float'>
>>> float('NAN') 
nan
>>> math.isnan(float('nan'))      #檢查是否為 nan
True
>>> math.inf 
inf
>>> type(math.inf)                      #inf 為 float 型態
<class 'float'>
>>> float('INF')   
inf
>>> math.isinf(float('inf'))         #檢查是否為 inf
True
>>> math.isnan(123)                  #檢查是否為 nan
False
>>> math.isinf(123)                    #檢查是否為 inf
False
>>> math.isnan('abc')                #不可傳入字串
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
TypeError: must be real number, not str


3. math 模組的函數 :

math 模組提供了三角, 指數, 對數, 反雙曲等超越函數如下表 :


 math 方法 說明
 sqrt(x) 傳回 x 的平方根
 pow(x, y) 傳回 x 的 y 次方
 exp(x) 傳回 x 的自然指數
 expm1(x) 傳回 x 的自然指數-1 (在 x 接近 0 時仍有精確值)
 log(x [, b]) 傳回 x 以 b 為基底的對數 (預設 b=e 自然對數)
 log10(x) 傳回 x 的常用對數 (以 10 為底數)
 degrees(x) 傳回弧度 x 的角度 (degree)
 radians(x) 傳回角度 x 的弧度 (radian)
 dist(p, q) 傳回兩個座標點 p, q 的歐幾里得距離 (畢式定理斜邊)
 hypot(coor) 傳回座標序列 coor 的歐幾里得距離
 sin(x) 傳回 x 的正弦值
 cos(x) 傳回 x 的餘弦值
 tan(x) 傳回 x 的正切值
 asin(x) 傳回 x 的反正弦值 (sin 的反函數)
 acos(x) 傳回 x 的反餘弦值 (cos 的反函數)
 atan(x) 傳回 x 的反正切值 (tan 的反函數)
 atan2(y, x) 傳回 y/x 的反正切值 (tan 的反函數)=atan(y/x)
 sinh(x) 傳回 x 的雙曲正弦值
 cosh(x) 傳回 x 的雙曲餘弦值
 tanh(x) 傳回 x 的雙曲正切值
 asinh(x) 傳回 x 的反雙曲正弦值=log(x+sqrt(x**2+1))
 acosh(x) 傳回 x 的反雙曲餘弦值=log(x+sqrt(x**2-1))
 atanh(x) 傳回 x 的反雙曲正切值=1/2*log((1+x)/(1-x))
 fabs(x) 傳回 x 的絕對值 (或稱模數, modulus)
 floor(x) 傳回浮點數 x 的向下取整數 (即小於 x 之最大整數)
 ceil(x) 傳回浮點數 x 的向上取整數 (即大於 x 之最小整數)
 trunc(x) 傳回浮點數 x 的整數部分 (捨去小數)
 modf(x) 傳回浮點數 x 的 (小數, 整數) 元組
 factorial(x) 傳回 x 階乘 (x!, x=整數)
 gcd(x, y) 傳回整數 x, y 之最大公因數
 comb(n, k) 傳回 n 取 k 的組合數 (不依序不重複)
 perm(n, k) 傳回 n 取 k 的組合數 (依序不重複)
 modf(x, y) 傳回 x/y 之精確餘數 (浮點數 float)
 fsum(iter) 傳回可迭代數值 iter 之精確總和
 isclose(x, y) 若 a, b 值很接近傳回 True (預設差小於 1e-9)
 isfinite(x) 若 x 不是 nan 或 inf 傳回 True, 否則 False
 isnan(x) 若 x 為 nan 傳回 True, 否則 False
 isinf(x) 若 x 為 inf 傳回 True, 否則 False


例如 :

#==========數值函數測試============
>>> import math
>>> math.fabs(-1.23)      #絕對值
1.23
>>> math.floor(1.5)        #向下取整數
1
>>> math.floor(-1.5)       #向下取整數
-2
>>> math.ceil(1.5)           #向上取整數
2
>>> math.ceil(-1.5)          #向上取整數
-1
>>> math.trunc(3.14159)    #捨去小數部分
3
>>> math.modf(3.14159)     #傳回浮點數之 (小數, 整數) 元組
(0.14158999999999988, 3.0)
>>> math.fmod(10, 3)          #傳回除法餘數 (浮點數)
1.0
>>> 10 % 3                            #傳回除法餘數 (整數)
1
>>> math.fsum([1, 2, 3])      #計算可迭代物件之元素和
6.0
>>> math.fsum((1, 2, 3))      #計算可迭代物件之元素和
6.0
>>> math.fsum(range(1, 11))      #計算可迭代物件之元素和
55.0

要注意 fmod() 與 modf() 的不同, fmod() 是取餘數, 表示 float modulus 之意; 而 modf() 用 Python 的取餘數運算子 % 得到的餘數為整數, 而用 math.fmod() 得到的是浮點數.

階乘函數 factorial() 在數學上 n 階乘表示為 n!=n*(n-1)*(n-2) ... *2*1, 但 0!=1!=1 :

#==========階乘函數============
>>> import math
>>> math.factorial(0)                   
1
>>> math.factorial(1) 
1
>>> math.factorial(2) 
2
>>> math.factorial(3) 
6
>>> math.factorial(10) 
3628800

math 官方文件中有排列函數 math.perm() 與組合函數 math.comb(), 但在 dir(math) 中卻沒有, 實際測試也確實不支援 :

>>> math.perm(8,3) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'math' has no attribute 'perm'
>>> math.comb(8,3) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'math' has no attribute 'comb'

這只好用 factorial() 來自行定義了, 排列函數 perm(n, k) 是指在 n 個不同元素中取出 k 個做排列, 總共有 n!/(n-k)! 排法 (注意, 排列是有順序的) :

def perm(n, k):
    if (n<0 or k<0):
        return None
    else:
        return math.factorial(n)/math.factorial(n-k)

組合函數 comb(n, k) 是指在 n 個不同元素中取出 k 個, 總共有 n!/[k!(n-k)!] 個取法 (注意, 組合是沒有順序的) :

def comb(n, k):
    if (n<0 or k<0):
        return None
    else:
        return math.factorial(n)/(math.factorial(k)*math.factorial(n-k))

例如 :

>>> import math   
>>> def perm(n, k): 
...     if (n<0 or k<0):   
...         return None 
...     else: 
...         return math.factorial(n)/math.factorial(n-k)   
...
>>> def comb(n, k): 
...     if (n<0 or k<0):   
...         return None   
...     else:   
...         return math.factorial(n)/(math.factorial(k)*math.factorial(n-k))   
...
>>> perm(8,3)        #8 取 3 做排列
336.0
>>> comb(49,6)      #49 取 6 做組合
13983816.0


#==========指數與對數函數=============
>>> import math
>>> math.sqrt(2)              #開平方
1.4142135623730951
>>> math.pow(2, 3)          #次方
8.0
>>> math.exp(1)               #自然指數
2.718281828459045
>>> math.expm1(1)          #自然指數少 1
1.718281828459045
>>> math.e                        #自然指數
2.718281828459045
>>> math.log(1)                #1 的對數為 0
0.0
>>> math.log(math.e)      #自然指數的對數為 1
1.0
>>> math.log10(1)            #1 的對數為 0
0.0
>>> math.log10(10)          #常用對數
1.0
>>> math.log(8, 2)            #8 以 2 為基底的對數=3, 因 2**3=8
3.0
>>> math.log(49, 7)          #49 以 7 為基底的對數=2, 因 7**2=49
2.0

注意, math 模組只能處理實數, 複數需用 cmath 處理, 例如 math.sqrt(-1) 會出現 "domain error" 錯誤訊息, 但 cmath.sqrt(-1) 則會傳回 1j, 例如 :

>>> math.sqrt(-1)           #math 無法處理 -1 開根號
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error   
>>> cmath.sqrt(-1)         #cmath 可處理 -1 開根號
1j 

#==========角度與弧度轉換============
>>> import math
>>> math.degrees(math.pi)          #弧度轉角度 pi=180
180.0
>>> math.degrees(math.tau)        #弧度轉角度 2pi=360
360.0
>>> math.degrees(math.pi/2)       #弧度轉角度 pi/2=90
90.0
>>> math.degrees(math.pi/4)       #弧度轉角度 pi/4=45
45.0
>>> math.degrees(1.234)               #弧度轉角度
70.70299191914359
>>> math.radians(70.703)             #角度轉弧度
1.234000141037551
>>> math.radians(30)                    #角度轉弧度
0.5235987755982988
>>> math.radians(45)                    #角度轉弧度
0.7853981633974483
>>> math.radians(60)                    #角度轉弧度
1.0471975511965976
>>> math.radians(90)                    #角度轉弧度
1.5707963267948966
>>> math.radians(180)                  #角度轉弧度 (pi)
3.141592653589793
>>> math.radians(360)                  #角度轉弧度 (2pi)
6.283185307179586

三角函數是圓函數, 一共有六個, 但 math 模組只提供了三個 : sin(), cos(), 與 tan(), 因為其他三個是這三個的倒數, 即 cot=1/tan, sec=1/cos, csc=1/sin, 其反函數也是如此. 參考 :

# 維基 : 三角函數


#==========三角函數============
>>> import math
>>> math.sin(math.pi/6) 
0.49999999999999994
>>> math.sin(math.pi/2)   
1.0
>>> math.sin(math.pi)   
1.2246467991473532e-16
>>> math.asin(1)   
1.5707963267948966
>>> math.asin(0)   
0.0

雙曲函數起源於解決達文西的懸鍊曲線問題 (例如吊橋與架高電纜以前被認為是拋物線, 事實上是雙曲線), 也應用在非歐幾何與相對論等. 雙曲函數跟三角函數長得很像, 函數名稱多了一個 h, 但其實與三角函數無關, 不過這兩個無關的函數在尤拉公式出現後卻統一在複變數理論下. 三角函數與雙曲函數在複平面上形狀是一樣的, 三角函數的週期是 2*pi, 而雙曲函數則是 2*pi*i.

雙曲函數定義如下 (定義域為實數) :




其實只要定義 sinh(), cosh(), 與 tanh() 即可, 另外三個只是倒數而已. 雖然雙曲函數雖然與三角函數無關, 但卻有類似三角函數之特性, 因為其公式除了正負號略為不同外均可套用, 而且在虛數圓角上與三角函數具有如下關係 :


參考 :

# 維基 : 雙曲函數
雙曲函數及反三角函數
雙曲函數的基本數性質
4980N031的學習歷程檔案
# 雙曲函數
可能是最好的讲解双曲函数的文章
python弧度制轉換 三角函數 反三角函數 雙曲 反雙曲


#==========雙曲函數============
>>> import math
>>> math.sinh(1)   
1.1752011936438014
>>> math.cosh(1) 
1.5430806348152437
>>> math.tanh(1) 
0.7615941559557649
>>> math.asinh(1.1752011936438014)    #反雙曲正弦
1.0
>>> math.acosh(1.5430806348152437)    #反雙曲餘弦
1.0
>>> math.atanh(0.7615941559557649)    #反雙曲正切
0.9999999999999999


4. cmath 模組的函數 :

cmath 模組的運算元是複數, 常數增加了複數的 infj 與 nanj, 方法則大部分與 math 相同, 不過沒有階乘函數 (因為複數沒有階乘), 但增加了如下三個與複數座標轉換有關的方法, 方便在卡式座標與極座標之間轉換 :


 cmath 方法 說明
 polar(x) 傳回複數 x 的極座標表示法元組 (r, p), r=長度, p=角度
 rect(r, p) 傳回極座標 (r, p) 的複數
 phase(x) 傳回複數 x 的弧度 (radian)


#==========cmath 測試============
>>> import math
>>> import cmath
>>> print(cmath.infj) 
infj
>>> print(cmath.nanj) 
nanj
>>> type(cmath.nanj)                               #複數類型
<class 'complex'>
>>> type(cmath.nan) 
<class 'float'>
>>> type(cmath.infj)                                 #複數類型
<class 'complex'>
>>> type(cmath.inf)     
<class 'float'>
>>> cmath.polar(1+1j)                              #複數轉極座標
(1.4142135623730951, 0.7853981633974483)
>>> cmath.rect(1.4142135623730951, 0.7853981633974483)   
(1.0000000000000002+1.0000000000000002j)         #極座標轉直角坐標
>>> cmath.phase(1+1j)                              #傳回複數弧度
0.7853981633974483
>>> math.degrees(cmath.phase(1+1j))     #弧度轉角度
45.0
>>> cmath.sinh(1+1j) 
(0.6349639147847361+1.2984575814159773j) 
>>> cmath.cosh(1+1j) 
(0.8337300251311491+0.9888977057628651j) 
>>> cmath.tanh(1+1j) 
(1.0839233273386946+0.2717525853195118j) 

我覺得 cmath.polar() 與 cmath.rect() 在學習交流電分析時蠻好用的.

沒有留言 :