【Python】累乗・べき乗の計算
実数または複素数 $a$ を複数回掛けることを $a$ の累乗とよびます。$a$ を $n$ 回掛ける場合は $a^n$ という記号で表し、「$a$ の $n$ 乗」(nth power of a) と読みます。$a$ を底 (base)、$n$ を指数 (exponent) といいます。「$a$ の肩に $n$ が載っている」と表現することもあります。
たとえば、$4.5$ を $3$ 回掛けることを $4.5$ の $3$ 乗とよび、$4.5^3$ と表わします。$4.5$ が底、$3$ が指数です。
Python では a の n 乗を、累乗演算子「**」を用いて a**n と表記します。Python で $4.5^3$ を計算してみましょう。
# PYTHON_POWER_01 # In[1] # 4.5の3乗を計算 print(4.5 ** 3) # 91.125
底 $a$ が $0$ 以外の数のとき、$a^0$ は $1$ と定義されています。
# In[2] # 256の0乗を計算 print(256 ** 0) # 1
数学においては、0の0乗は分野によって定義・未定義が分かれますが、Python では $0^0 = 1$ と定義されています。
# In[3] # 0の0乗 print(0 ** 0) # 1
複素数の累乗も計算できます。
$2+3i$ の $4$ 乗を計算させてみましょう。
# In[4] # 2+3iの4乗 val = (2 + 3j) ** 4 print(val) # (-119-120j)
指数 $n$ が負であるとき、$a^n$ は $a$ の逆数 $1/a$ を $n$ 乗する ($1$ を $a$ で $n$ 回除算する) ことを意味します。
\[a^{-n}=\left ( \frac{1}{a} \right )^n\]
たとえば、$5^{-3}$ は $1/5$ の $3$ 乗です。
Python に $5^{-3}$ を計算させてみましょう。
# In[5] # 5の立方根を計算 val = 5 ** (-3) print(val) # 0.008
Python では組み込み関数 pow(x, n) を使って $x^n$ を計算することもできます。たとえば、上のコードは pow() を使って次のように書くことができます。
# In[6] # 5の立方根を計算 val = pow(5, -3) print(val) # 0.008
複雑な累乗計算は、pow() で表記したほうが見やすいので(** を使っていると目がチカチカするので)、以降のコードでは pow() を使うことにします。
指数 $n$ を実数 $x$ や複素数 $z$ まで拡張した場合、$a^x$ や $a^z$ を冪乗(べき乗)演算とよびます。たとえば、指数 $n$ を有理数に拡張した冪乗を考えてみましょう(有理数とは分数の形で表せる数のことです)。$n$ が正整数であるとき、$a^{1/n}$ を $a$ の $n$ 乗根とよび、$x^n=a$ の解として定義されます。$2$ 乗根と $3$ 乗根については、それぞれ平方根、立方根とよばれることが一般的です。
$a$ が正数である場合、慣例として正符号の解を採用することになっています。たとえば、$x^2=9$ より、$9$ の平方根は $\pm 3$ ですが、そのうち $3$ を採用します。Python のべき根演算もそのようになっています。
# In[7] # 9の1/2乗を計算 print(9 ** 0.5) # 3.0
$a^{1/n}$ が定義されたことにより、$a^{m/n}$ は $a^{1/n}$ を $m$ 回掛けた数として定義できます。たとえば、$9^{3/2}$ は
\[9^{3/2}=(9^{1/2})^3=3^3=27\]
となります。Python でも確認しておきましょう。
# In[8] # 9の3/2乗を計算 val = pow(9, 3/2) print(val) # 27.0
底が負の場合を考えてみます。$(-5)^3=-125$ なので、$-125$ の立方根 $(-125)^{1/3}$ は $-5$ とするのが数学では一般的です。ところが、Python でこれをそのまま計算させると以下のような結果となります。
# In[9] # -125の立方根を計算 val = pow(-125, 1/3) print(val) # (2.5+4.330127018922192j)
複素数型の値が戻ってきました。結果が間違っているわけではありません。$2.5+4.330i…$ を $3$ 乗しても $-125$ となるので、確かに $-125$ の立方根のひとつです。ただし、この値を採用することは必ずしも一般的ではありません。実数根を得たいのであれば、以下のように工夫する必要があります。
# In[10] # -125の実数立方根を計算 x = - pow(abs(-125), 1/3) print(x) # -4.999999999999999
実数の底 x と有理指数 y を与えて、実数のみを返すべき乗関数は次のように定義できます。
# In[11] # 実数根のみを返すべき乗関数 def pow_real(x, y): if x >= 0: p = pow(x, y) else: p = - pow(abs(-x), y) return p
pow_real() 関数を使って、$-256$ の $4$ 乗根を計算してみます。
# In[12] # -256の4乗根 val = pow_real(-256, 0.25) print(val) # -4.0
指数はさらに無理数 (循環しない無限小数) にまで拡張できます。たとえば、$10^{\sqrt{3}}$ のようなべき乗も定義されています。Python でも形の上では無理指数のべき乗を計算できます。
# In[13] import math # 10の√2乗を計算 val = pow(10, math.sqrt(2)) print(val) # 25.954553519470085
しかし、計算機で無限小数は扱えません。無理数を適当な桁で打ち切った有理数で近似します。たとえば、$\sqrt{2}$ は $17$ 桁の有理数によって近似されます。
# In[14] # √2の近似値 val = math.sqrt(2) # 桁数を計算 disits = len(str(val)) - 1 print("math.sqrt(2):", val) print("桁数:", disits) # math.sqrt(2):1.4142135623730951 # 桁数:17
したがって、pow(10, math.sqrt(2)) は有理指数のべき乗 $10^{1.4142135623730951}$ を計算していることになります。数値計算でも便宜上、実数という用語を使いますが、厳密には一定の桁数に限定された有理数しか扱えません (あちこちに穴が開いた不連続な数直線をイメージしてください。コンピュータの世界では $\sqrt{2}$ という目盛がありません)。
pow()
Python にビルト・インされている(組込まれている)pow() 関数は次のような記法で呼びだします。
pow(x, y [,z])
引数の指定が 2 つであれば、算術演算子 ** と同じはたらきをします。
# PYTHON_POWER # In[1] # べき乗演算 a = pow(10, 2) b = pow(10, 1.5) c = pow(10, -2) d = pow(10.0, 3) e = pow(2 + 3j, 3) print("pow(10, 2):", a) print("pow(10, 1.5):", b) print("pow(10, -2):", c) print("pow(10.0, 3):", d) print("pow(2 + 3j, 3):", e) # pow(10, 2): 100 # pow(10, 1.5): 31.622776601683793 # pow(10, -2): 0.01 # pow(10.0, 3): 1000.0 # pow(2 + 3j, 3): (-46+9j)
オプション引数 z を指定すると、x の y 乗を z で割ったときの剰余(余り)を返します。
# In[2] # 10の2乗を7で割った余り a = pow(10, 2, 7) print(a) # 2
pow(x, y, z) は pow(x, y) % z よりも高速で処理できるように設計されています。べき乗の剰余というのは、数論の分野で多用される演算です。
math.pow()
Python 標準ライブラリの mathモジュールをインポートすると、math.pow() が使えるようになります。
math.pow(x, y)
算術演算子「**」や組み込みの pow()関数とは異なり、math.pow()関数は与えられた 2 つの引数の型が何であっても、浮動小数点数型に変換して演算します。もちろん戻り値も浮動小数点数となります。
# PYTHON_MATH_POWER # In[1] import math # べき乗演算 a = math.pow(2, 10) b = math.pow(81, 1/4) c = math.pow(3, -2) d = math.pow(2.0, 10) e = math.pow(0, 0) print("math.pow(2, 10):", a) print("math.pow(81, 1/4):", b) print("math.pow(3, -2):", c) print("math.pow(2.0, 10) =", d) print("math.pow(0, 0):", e) # math.pow(2, 10): 1024.0 # math.pow(81, 1/4): 3.0 # math.pow(3, -2): 0.1111111111111111 # math.pow(2.0, 10) = 1024.0 # math.pow(0, 0): 1.0
複素数型は浮動小数点数型に変換できないので、math.pow() の引数に複素数を指定すると TypeError を返します。
# In[2] val = math.pow(1+1j, 3) print(val) # TypeError: can't convert complex to float
底 x に負数を指定したとき、指数 y に整数以外の数値を指定すると ValueError を返します。
# In[3] # (-2)の1.5乗 val = math.pow(-2, 1.5) # ValueError: math domain error
ただし、y が浮動小数点数型であっても、1.0 や 2.0 のように値が整数であれば問題ありません。
# In[4] # math.pow()関数によるべき乗演算 a = math.pow(-2, 3.0) print(a) # -8.0
このように、math.pow() は算術演算子「 ** 」や組み込みの pow() に比べると演算機能が限定されていますが、これは実数範囲での指数演算の規則に合致するものです。これは通常の計算においては、おかしな値が入れられたときにエラーを返して警告してくれる math.pow() のほうが利用しやすいでしょう。「負数の非整数乗」のような演算は用途が限られているからです。
numpy.power()
numpy.power() は第 1 引数に受け取った配列の各要素を、第 2 引数の配列の各要素でべき乗するユニバーサル関数です。
# NUMPY_POWER # In[1] # 底となる配列を定義 x = np.array([1, 2, 3, 4, 5]) # 指数部の配列を定義 n = np.array([1, 2, 1, 2, 1]) # xのn乗を計算 y = np.power(x, n) print(y) # [ 1 4 3 16 5]
第 1 引数と第 2 引数の間にはブロードキャスト機能がはたらきます。たとえば、第 2 引数に整数 k を渡した場合、第 1 引数のすべての要素を k 倍します。
# In[2] # xの各要素を2乗 z = np.power(x, 2) print(y) # [ 1 4 3 16 5]
実数範囲を超えたベキ演算
Python の累乗演算子「**」もしくは組み込みの pow() を使うと、「-10 の 0.2 乗」や「 2 の i 乗」といった演算をさせても値を返してきます。
# PYTHON_COMPLEX_POWER # 底が負数のベキ演算 a = (-10) ** 0.2 # 複素数ベキ b = 2 ** 1j print("a =", a) print("b =", b) # a = (1.282205526970205+0.931576844987379j) # b = (0.7692389013639721+0.6389612763136348j)
この演算の意味を理解するには「複素解析学」という高度な専門知識が必要となります。ある分野の研究には、こうした演算も必要となるかもしれません。しかし、あくまで実数範囲で指数計算を行なう場合は、この機能はかえって危険なものとなりえます。実数範囲で定義されないはずの仕方で引数が指定されたとしても、例外処理がなされずに値を返してくるからです。上のサンプルコードにあるように「$-10$ の $0.2$ 乗」は引数がともに実数なので、いかにも「ありそうな演算」に思えて見過ごしてしまうかもしれませんが、演算結果は複素数となります。実数範囲でベキ演算を安全に行うためには、演算機能を制限した math.pow() を使用することをおすすめします。
コメント
下記は誤植と思われますので、ご確認ください。
In[14] プログラムの上の文で、√2 は 18 桁の有理数 → √2 は 17 桁の有理数
In[14] プログラムで、len(str(val)) → len(str(val)) – 1
In[14] プログラムの実行結果で、桁数: 18 → 桁数: 17
あ、そうですね! len(str(val)) は小数点 (.) も含めて数えてしまうので、桁数を求めるのは len(str(val)) – 1 としなければいけませんでした。それにしても、よく気づきましたね …