【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$ を有理数に拡張したべき乗も定義されています (有理数とは分数の形で表せる数のことです)。$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()
標準ライブラリの 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]
実数範囲を超えたベキ演算
算術演算子「**」もしくは組み込みの 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 としなければいけませんでした。それにしても、よく気づきましたね …