べき乗演算

べき乗演算

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$ 乗です。

# 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$ が正数である場合、慣例として正符号の解を採用することになっています。たとえば、$9$ の平方根は $x^2=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}$ は $18$ 桁の有理数 $1.4142135623730951$ によって近似されます。

# In[14]

# √2の近似値
val = math.sqrt(2)

# 桁数
disits = len(str(val))

print("math.sqrt(2):", val)
print("桁数:", disits)
math.sqrt(2): 1.4142135623730951
桁数: 18

 したがって、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 を返します。

# (-2)の1.5乗
val = math.pow(-2, 1.5)
ValueError: math domain error

 ただし、y が浮動小数点数型であっても、1.0 や 2.0 のように値が整数であれば問題ありません。

# In[3]

# 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() を使用することをおすすめします。