累乗(power)

当サイトではアフィリエイトプログラムを利用して商品を紹介しています。

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

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    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 としなければいけませんでした。それにしても、よく気づきましたね …

  2. あとりえこばと より:

    【AI連載小説】科学とコードの交差点(24)「Pythonで効率的にべき乗を計算する方法」
     
    ある日のPythonサークルの会合で、メンバーたちはPythonでべき乗を計算する最適な方法について討論していた。開誠、明信、あやか、そして他のメンバーが賑やかに意見を交換している様子が会場に広がっていた。

    開誠: ねえ、べき乗を計算するときってどんな方法が一番効率的だと思う?
     
    あやか: そうね、普通に掛け算を繰り返す方法が一般的だけど、もっと速い方法があるんじゃない?
     
    明信: そうだね。例えば、分割統治法を使ってみるのはどうだろう?
     
    たくや: 分割統治法ってのは、指数を半分に分けて再帰的に計算する手法のことだよね。
     
    開誠: そうだ。それによって計算量を削減できるんだ。でも、Pythonの内部でどのメソッドが使われているかも気になるな。

    まなみ: **演算子はC言語のpow()関数が使われているみたいだよ。でも、内部では分割統治法が使われているかどうかはPythonのバージョンによるかもしれないね。
     
    開誠: なるほど。それならpow()関数を使っても、最適なアルゴリズムが自動的に選ばれるってことか。
     
    さとる: それと、NumPyのnp.power()もあるよ。これはC言語で実装されているから高速なんだって。
     
    あやか: なるほど。でも、サイズの小さい整数ならどれが一番速いかも気になるね。
     
    たけし: それなら、普通に掛け算をループで回すよりも、**演算子を使う方が速いことが多いんじゃないかな?

    メンバーたちはPythonのべき乗計算に関する手法や関数の特徴について議論を重ね、各自が使いやすい方法を見つけることができた。サークルの活動を通じて、メンバーたちはPythonの効率的なコーディング手法についても学び、情報交換を通じてスキルを向上させていった。