静かなる名辞

pythonとプログラミングのこと


【python】べき乗とべき根の計算

 べき乗はx^n、べき根は\sqrt[n]{x}です。では、pythonではどう書くのでしょうか。

 2乗とかsqrtくらいはわかっても、n乗根あたりになるとすぐ出てこないという人も多いのでは? そこで、説明を書きます。

 目次

スポンサーリンク



組み込み関数powを使う方法

 powという組み込み関数があります。まあ、要は達します。

>>> pow(2, 10)
1024
>>> pow(0.5, 10)
0.0009765625
>>> pow(1.5, 3.5)
4.133513940946613

 見たところ浮動小数点でも大丈夫。

 ドキュメントによると、第三引数を指定することで「x の y 乗に対する z の剰余」を計算できるそうですが、何に使えるのでしょうかね・・・。

組み込み関数 — Python 3.7.3 ドキュメント

 2018年11月26日追記:
 第三引数の件についてコメントでご指摘をいただきました。これを効率的に計算できることで、公開鍵暗号へ応用できるということのようです。

記事の中で「第三引数を指定することで「x の y 乗に対する z の剰余」を計算できる」のが何のためだろうと書かれていたので、回答になるかもと思いコメントします。
共通鍵暗号の鍵を通信相手と交換する方法に、デフィー・ヘルマン鍵交換という方式があります。その方式では、
(1) Ya = r の Xa乗 に対する q の余剰(Ya: ユーザaの公開鍵, r: qの原始根, Xa: ユーザaの秘密鍵, q: 素数)
(2) K = Ya の Xb乗 に対する q の余剰(K: 共通鍵, Ya: ユーザaの公開鍵, Xb: ユーザbの秘密鍵, q: 素数)
という計算が出てきます。

powを使えば例えば(1)は Ya = pow (r, Xa, q) だけで計算できるということです。

(tomoさんのコメントより引用)

 関連しそうなwikipediaのページを貼っておきます。

冪剰余 - Wikipedia
ディフィー・ヘルマン鍵共有 - Wikipedia
離散対数 - Wikipedia

べき乗演算子を使う方法

 べき乗の計算では、pow関数と同じことが演算子でもできます。

>>> 2**10
1024
>>> 0.5**10
0.0009765625
>>> 1.5**3.5
4.133513940946613

 powを多用すると式がごちゃごちゃするので、こちらを使いたいところです。

 ただ、罠があるようです。

Python のべき乗演算子に潜む罠 | CUBE SUGAR STORAGE

 単項演算子の-などと組み合わせる場合、べき乗演算子が先に評価されます。どういうこと? と思うかもしれませんが、要するに

>>> -1**2  # WTF!?
-1
>>> -(1**2)  # つまりはこういうこと
-1
>>> (-1)**2  # 本当に欲しかったもの
1

 思い通りの結果を得たければカッコを多用すべきであり、そうするとなんかpowでも良いような気もしてきます。

numpyに頼る方法

 np.powerがあります。

>>> np.power(2, 10)
1024
>>> np.power(0.5, 10)
0.0009765625
>>> np.power(1.5, 3.5)
4.133513940946613

 見たところ、型の取扱も含めてほぼ同じ。まあ、これを単体で使うメリットは特に感じません。numpy配列を相手にするなら、ありかも(ただしnumpy配列にべき乗演算子を使うという選択肢もある)。なお、片方が配列で片方がスカラー、とか配列同士、というケースでは、

>>> import numpy as np
>>> np.power([1,2,3], 2)
array([1, 4, 9])
>>> np.power(2, [1,2,3])
array([2, 4, 8])
>>> np.power([1,2,3], [4,5,6])
array([  1,  32, 729])

 こんな扱いになります。特に難しいことはないです。

n乗根について

 n乗根(べき根)がわからなかった人は、\sqrt[2]{x} = x^\frac{1}{2}を思い出しましょう。

>>> 2**10  # べき乗(10乗)
1024
>>> 1024**(1/10)  # べき根(10乗根)
2.0

 小数のべき乗が計算できれば、べき根も計算できるということです。

どれが速いの?

 timeitで簡単に比較してみます。私はIPython使いではないので、モジュールimportで使います。

>>> import numpy as np
>>> import timeit
>>> timeit.timeit(lambda : [[x**y for y in range(30)] for x in range(30)], number=1000)
0.37399847699998645
>>> timeit.timeit(lambda : [[pow(x,y) for y in range(30)] for x in range(30)], number=1000)
0.4485901359998934
>>> timeit.timeit(lambda : [[np.power(x,y) for y in range(30)] for x in range(30)], number=1000)
1.3186961119999978

 スカラーに対してはべき乗演算子一択。

 numpy配列だと、べき乗演算子とnp.powerはどちらが上でしょうか。

>>> timeit.timeit(lambda : np.arange(1000)**np.arange(1000), number=100000)
1.4120757860000595
>>> timeit.timeit(lambda : np.power(np.arange(1000), np.arange(1000)), number=100000)
1.402805570000055

 そもそも冗談みたいに速いんですが(numberに注目)、速度差自体は計測誤差レベル。一番コアな部分は同じ処理なのでしょう。

まとめ

 pythonではべき乗・べき根を計算する方法が何通りかあります。とはいえ、べき乗演算子で大抵の用途では用が済むようなので、罠に気をつけて(負の数が絡むときだけ気をつける)使えば良いと思いました。

 あと、numpyはやっぱりすごいなぁ・・・