M.Hiroi's Home Page

Lightweight Language

お気楽 Python3 プログラミング超入門

[ Home | Light | Python3 ]

●複素数

近年、多くのプログラミング言語で「複素数 (complex number)」がサポートされるようになりました。たとえば、C言語では 1999 年に発行された規格 C99 で複素数型が導入されました。Go 言語や Python でも複素数型をサポートしていますし、複素数用の標準ライブラリを用意している言語 (C++, Ruby, Haskell など) も多くあります。今回は Python の複素数について説明します。

●Python の数

Python の数はプリミティブな型として、整数 (int)、実数 (float)、複素数 (complex) の 3 種類があります。Python3 の整数はメモリの許す限り任意の精度で扱うことができます。標準モジュール fractions をインポートすると、分数 (fraction) を扱うこともできます。分数は分子と分母を整数で保持しているので、整数と同様に任意の精度で扱うことができます。

実数は浮動小数点数 (floating point number) として表現されます。浮動小数点数には IEEE 754 という標準仕様があり、近代的なプログラミング言語のほとんどは、IEEE 754 に準拠した浮動小数点数をサポートしています。浮動小数点数はすべての小数を正確に表現することはできません。このため、実数は近似的な値になります。

IEEE 754 には通常の数値以外にも、負のゼロ (-0.0)、正負の無限大 (∞, -∞)、NaN (Not a Number, 非数) といった値が定義されています。これらの値は Python でも取り扱うことができます。負のゼロは -0.0、正負の無限大は inf と -inf、NaN は nan と表記されます。

●無限大

一般に、無限大は値のオーバーフロー、ゼロ除算 (数値 / 0.0)、数学関数の計算結果 (たとえば log(0.0)) などで発生します。なお Python では浮動小数点数のゼロ除算や log(0.0) はエラー (例外) を送出するので無限大にはなりません。無限大は float("inf"), float("-inf") で生成する、または標準モジュール math に定義されている定数 inf を使います。

簡単な実行例を示します。

>>> 1e308
1e+308
>>> 1e309
inf
>>> 1e308 * 2
inf
>>> -1e308
-1e+308
>>> -1e309
-inf
>>> -1e308 * 2
-inf
>>> float("inf")
inf
>>> float("-inf")
-inf
>>> 1.0 / 0.0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: float division by zero
>>> import math
>>> math.log(0.0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error

Python の場合、無限大の判定はモジュール math の関数 isinf を使います。

>>> math.inf
inf
>>> -math.inf
-inf
>>> math.isinf(math.inf)
True
>>> math.isinf(-math.inf)
True
>>> math.isinf(1.2345)
False
>>> math.isinf(-1.2345)
False

無限大は他の数値と比較したり演算することもできますが、結果が NaN になることもあります。

>>> a = math.inf
>>> b = -math.inf
>>> a == a
True
>>> a != b
True
>>> a == b
False
>>> a < b
False
>>> a > b
True
>>> a > 0
True
>>> a < 0
False
>>> b > 0
False
>>> b < 0
True
>>> a + 10
inf
>>> a - 10
inf
>>> a * 10
inf
>>> a / 10
inf
>>> a + a
inf
>>> a * a
inf
>>> a - a
nan
>>> a / a
nan
>>> a + b
nan
>>> a * 0.0
nan

●負のゼロ

負のゼロ (-0.0) は、計算結果が負の極めて小さな値でアンダーフローになったとき発生します。また、正の値を負の無限大で除算する、負の値を正の無限大で除算する、負の値と 0.0 を乗算しても -0.0 が得られます。

>>> -1e-323
-1e-323
>>> -1e-324
-0.0
>>> -1e-323 / 2
-5e-324
>>> -1e-323 / 4
-0.0
>>> 1.0 / -math.inf
-0.0
>>> -1.0 / math.inf
-0.0
>>> -1.0 * 0.0
-0.0

標準では、演算子 (Python では ==) による 0.0 と -0.0 の比較は等しいと判定されます。

>>> -0.0
-0.0
>>> 0.0 == -0.0
True
>>> 0.0 != -0.0
False
>>> -0.0 == 0.0
True
>>> -0.0 != 0.0
False

なお、-0.0 は数学関数 (モジュール math の関数 atan2 など) や複素数の演算処理などで使われます。

>>> math.sqrt(0.0)
0.0
>>> math.sqrt(-0.0)
-0.0
>>> math.atan2(0.0, -1.0)
3.141592653589793
>>> math.atan2(-0.0, -1.0)
-3.141592653589793

●非数

NaN は数ではないことを表す特別な値 (非数) です。一般的には 0.0 / 0.0 といった不正な演算を行うと、その結果は NaN になりますが、0.0 で除算すると Python ではエラーが送出されます。nan は float("nan") で生成したり、math.nan で取得することができます。

>>> math.nan
nan
>>> float("nan")
nan
>>> math.nan / math.nan
nan
>>> math.inf / math.inf
nan

Python の場合、NaN はモジュール math の関数 isnan で判別することができます。関数 math.isfinite は引数が無限大でも NaN でもなければ真を返します。

>>> math.isnan(math.nan)
True
>>> math.isnan(1.2345)
False
>>> math.isfinite(math.inf)
False
>>> math.isfinite(math.nan)
False
>>> math.isfinite(1.2345)
True

●Python の複素数

数学では複素数 z を \(x + iy\) と表記します。x を実部、y を虚部、i を虚数単位といいます。虚数単位は 2 乗すると -1 になる数です。実部と虚部の 2 つの数値を格納するデータ構造を用意すれば、プログラミング言語でも複素数を表すことができます。Python では実部と虚部を実数で保持しています。

Python は複素数 \(z = x + iy\) を x + yj と表記します。実部 x を省略すると x は 0.0 になります。虚部 y を省略することはできません。1 + j はエラーになるので、1 + 1j と書いてください。複素数はコンストラクタ complex(x, y) で生成することもできます。複素数のデータ型は complex です。

複素数 z の実部はメソッド real で、虚部はメソッド imag で取得することができます。複素数の虚部の符号を反転することを「複素共役」といいます。複素共役はメソッド conjugate() で求めることができます。

簡単な例を示しましょう。

>>> 1 + 2j
(1+2j)
>>> complex(3, 4)
(3+4j)
>>> a = 1.234-5.6789j
>>> a
(1.234-5.6789j)
>>> a.real
1.234
>>> a.imag
-5.6789
>>> a.conjugate()
(1.234+5.6789j)
>>> type(a)
<class 'complex'>

>>> 1j
1j
>>> -1j
(-0-1j)
>>> 0-1j
-1j
>>> -0.0-1j
(-0-1j)
>>> 1+0.0j
(1+0j)
>>> 1-0.0j
(1+0j)
>>> complex(1, 0.0)
(1+0j)
>>> complex(1, -0.0)
(1-0j)

Python で -1j と入力すると、実部は負のゼロになるようです。また、1-0.0j と入力しても虚部は負のゼロにはなりません。Python で虚部に負のゼロを指定したいときはコンストラクタ complex を使ってください。

複素数は極形式 \(z = |z|(\cos \theta + i \sin \theta)\) で表すことができます。このとき、\(|z|\) を絶対値、\(\theta\) を偏角といいます。絶対値は関数 abs で求めることができます。偏角は複素平面において正の実軸とベクトル (x, y) との角度を表します。偏角を求めるには標準モジュール cmath の関数 phase を使います。phase の返り値 \(\theta\) は \(-\pi \leq \theta \leq \pi \) (\(\pi\) : 円周率) です。

このほかに、複素数の絶対値と偏角をタプルに格納して返す関数 cmath.polar()、絶対値と偏角から複素数を生成する関数 cmath.rect() が用意されています。

簡単な例を示しましょう。

>>> b = 1+1j
>>> b
(1+1j)
>>> abs(b)
1.4142135623730951
>>> cmath.phase(b)
0.7853981633974483
>>> cmath.polar(b)
(1.4142135623730951, 0.7853981633974483)
>>> cmath.rect(abs(b), cmath.phase(b))
(1.0000000000000002+1j)

>>> cmath.phase(complex(1.0, 0.0))
0.0
>>> cmath.phase(complex(1.0, 1.0))
0.7853981633974483
>>> cmath.phase(complex(0.0, 1.0))
1.5707963267948966
>>> cmath.phase(complex(-1.0, 1.0))
2.356194490192345
>>> cmath.phase(complex(-1.0, 0.0))
3.141592653589793
>>> cmath.phase(complex(1.0, -1.0))
-0.7853981633974483
>>> cmath.phase(complex(0.0, -1.0))
-1.5707963267948966
>>> cmath.phase(complex(-1.0, -1.0))
-2.356194490192345
>>> cmath.phase(complex(-1.0, -0.0))
-3.141592653589793

Python の場合、-1.0+0.0j の偏角 \(\theta\) は \(\pi\) になり、-1.0-0.0j の偏角は \(-\pi\) になります。ゼロと負のゼロを区別しないプログラミング言語では、偏角 \(\theta\) の範囲を \(-\pi \lt \theta \leq \pi\) に制限して、-1.0 + 0.0j (== -1.0 - 0.0j) の偏角を \(\pi\) とします。

実部と虚部の値は inf, -inf, nan になることもあります。モジュール cmath にも inf と nan が定義されています。

>>> complex(cmath.inf, cmath.inf)
(inf+infj)
>>> complex(cmath.inf, -cmath.inf)
(inf-infj)
>>> complex(cmath.nan, cmath.inf)
(nan+infj)
>>> complex(cmath.nan, cmath.nan)
(nan+nanj)
>>> 1e300+1e300j * 1e300+1e300j
(1e+300+infj)

これらの判別は標準モジュール cmath の関数 isinf, isnan, isfinite で行います。isinf は実部または虚部が無限大であれば真を返します。isnan は実部または虚部が NaN であれば真を返します。isfinite は実部と虚部が有限な値であれば真を返します。

>>> cmath.isinf(complex(1, cmath.inf))
True
>>> cmath.isinf(complex(cmath.inf, 1))
True
>>> cmath.isinf(complex(1, 1))
False
>>> cmath.isnan(complex(1, cmath.nan))
True
>>> cmath.isnan(complex(cmath.nan, 1))
True
>>> cmath.isnan(complex(1, 1))
False
>>> cmath.isfinite(complex(1, 1))
True
>>> cmath.isfinite(complex(1, cmath.inf))
False
>>> cmath.isfinite(complex(cmath.nan, 1))
False

●複素数の四則演算

複素数の四則演算は次のようになります。

\( \begin{array}{l} (a + bi) + (c + di) = (a + c) + (b + d)i \\ (a + bi) - (c + di) = (a - c) + (b - d)i \\ (a + bi) \times (c + di) = (ac - bd) + (bc + ad)i \\ (a + bi) \div (c + di) = \dfrac{ac + bd + (bc - ad)i}{c^2 + d^2} \end{array} \)

これらの演算は Python の関数 +, -, * , / で行うことができます。複素数の場合、大小の比較演算は使えませんが、等値の判定は演算子 == や != で行うことができます。簡単な例を示しましょう。

>>> a = 1 + 2j
>>> a
(1+2j)
>>> b = 3 + 4j
>>> b
(3+4j)
>>> a + b
(4+6j)
>>> a - b
(-2-2j)
>>> a * b
(-5+10j)
>>> a / b
(0.44+0.08j)
>>> a == a
True
>>> a == b
False
>>> a != b
True

●複素数の指数関数と対数関数

複素数を引数にとる指数関数はオイラー (Euler) の公式から導くことができます。

\( \begin{array}{l} e^{i\theta} = \cos \theta + i \sin \theta \quad (Euler's formula) \\ \begin{eqnarray} e^{x+iy} &=& e^x e^{iy} \\ &=& e^x (\cos y + i \sin y) \end{eqnarray} \end{array} \)

複素数の対数関数は複素数 z を絶対値 \(|z|\) と偏角 \(\theta\) を使って導くことができます。

\( \begin{array}{l} x + iy = |z| e^{i\theta} \\ \begin{eqnarray} \log_e (x + iy) &=& log_e |z| e^{i\theta} \\ &=& log_e |z| + log_e e^{i\theta} \\ &=& log_e |z| + i\theta, \quad (-\pi \leq \theta \leq \pi) \end{eqnarray} \end{array} \)

複素数 x, y のべき乗 \(x^y\) は次式で求めることができます。

\(x^y = e^{y\log x}\)

標準モジュール cmath の関数 exp と log は複素数に対応しています。べき乗も演算子 ** で求めることができます。簡単な例を示しましょう。

>>> cmath.pi
3.141592653589793
>>> cmath.exp(0.0)
(1+0j)
>>> cmath.exp(complex(0.0, cmath.pi / 4))
(0.7071067811865476+0.7071067811865475j)
>>> cmath.exp(complex(0.0, cmath.pi / 2))
(6.123233995736766e-17+1j)
>>> cmath.exp(complex(0.0, cmath.pi))
(-1+1.2246467991473532e-16j)
>>> cmath.exp(complex(0.0, -cmath.pi))
(-1-1.2246467991473532e-16j)
>>> cmath.exp(complex(1.0, 1.0))
(1.4686939399158851+2.2873552871788423j)

>>> cmath.log(1.0 + 1.0j)
(0.34657359027997264+0.7853981633974483j)
>>> cmath.log(1.0 + 0.0j)
0j
>>> cmath.log(0.0 + 1.0j)
1.5707963267948966j
>>> cmath.log(1.0 - 1.0j)
(0.34657359027997264-0.7853981633974483j)
>>> cmath.log(1e300 + 1e300j)
(691.1221014884936+0.7853981633974483j)

>>> (1.0 + 1.0j) ** 0
(1+0j)
>>> (1.0 + 1.0j) ** 1
(1+1j)
>>> (1.0 + 1.0j) ** 2
2j
>>> (1.0 + 1.0j) ** 3
(-2+2j)
>>> (1.0 + 2.0j) ** (3.0 + 4.0j)
(0.129009594074467+0.03392409290517014j)
>>> (1.0 + 1.0j) ** (1.0 + 1.0j)
(0.2739572538301211+0.5837007587586147j)

関数 \(\log z \ (z = x + iy)\) は負の実軸 (\(-\infty \lt x \lt 0\)) において、x + 0.0j と x - 0.0j では値が異なります。

>>> cmath.log(complex(-1.0, 0.0))
3.141592653589793j
>>> cmath.log(complex(-1.0, -0.0))
-3.141592653589793j
>>> cmath.log(complex(-2.0, 0.0))
(0.6931471805599453+3.141592653589793j)
>>> cmath.log(complex(-2.0, -0.0))
(0.6931471805599453-3.141592653589793j)
>>> cmath.log(complex(-1e300, 0.0))
(690.7755278982137+3.141592653589793j)
>>> cmath.log(complex(-1e300, -0.0))
(690.7755278982137-3.141592653589793j)

このように、関数 \(\log z\) は負の実軸上で 2 つの値を持ちます。数学では値を一つ返す関数を「一価関数」、複数の値を返す関数を「多価関数」といいます。ここで、定義域を制限することで多価関数を一価関数にみなすことを考えます。関数 \(\log z\) の場合、負の実軸を定義域から取り除けば、\(\log z\) を一価関数とみなすことができるわけです。

参考 URL 7 によると、この取り除いた領域を branch cut と呼ぶそうです。プログラミングでは branch cut を定義域から取り除くのではなく、その領域では不連続な関数とするそうです。モジュール cmath のドキュメントには、\(\log\) は「分枝切断を一つもち、0 から負の実数軸に沿って \(-\infty\) へと延びており」と記述されています。Python のマニュアルに合わせて、本稿でも branch cut を「分枝切断」と記述することにします。

プログラミング言語の場合、0.0 と -0.0 を区別する処理系であれば、Python のように 2 つの値を区別することができます。0.0 と -0.0 を区別しない処理系では、偏角 \(\theta\) の範囲を \(-\pi \lt \theta \leq \pi\) に制限することで、\(\log z\) の返り値を (\(-\pi\) を取り除いて) 一つにすることができます。

●複素数の三角関数

複素数の三角関数の定義は、オイラーの公式から導かれる式の \(\theta\) を複素数 \(z\) に変えたものになります。

\(\sin -\theta = -\sin \theta, \ \cos -\theta = \cos \theta \) より
\( \begin{eqnarray} e^{i(-\theta)} &=& \cos -\theta + i \sin -\theta \\ &=& \cos \theta - i \sin \theta \end{eqnarray} \)

\( \begin{array}{l} e^{i\theta} + e^{-i\theta} = 2 \cos \theta \\ \cos \theta = \dfrac{e^{i\theta} + e^{-i\theta}}{2} \\ e^{i\theta} - e^{-i\theta} = 2i \sin \theta \\ \sin \theta = \dfrac{e^{i\theta} - e^{-i\theta}}{2i} \end{array} \)

\(\theta\) を複素数 z に置き換えた式が三角関数の定義になる

\( \begin{eqnarray} \sin z = \dfrac{e^{iz} - e^{-iz}}{2i} \\ \cos z = \dfrac{e^{iz} + e^{-iz}}{2} \end{eqnarray} \)

\(\sin z, \cos z\) に純虚数 \(ix\) を与えると双曲線関数 (\(\sinh x, \cosh x\)) になります。

双曲線関数の定義
\(\begin{eqnarray} \sinh x = \dfrac{e^x - e^{-x}}{2} \\ \cosh x = \dfrac{e^x + e^{-x}}{2} \end{eqnarray}\)
\(\begin{eqnarray} \sin ix &=& \dfrac{e^{iix} - e^{-iix}}{2i} \\ &=& \dfrac{e^{-x} - e^x}{2i} \times \dfrac{-i}{-i} \\ &=& i \dfrac{e^x - e^{-x}}{2} \\ &=& i \sinh x \\ \cos ix &=& \dfrac{e^{iix} + e^{-iix}}{2} \\ &=& \dfrac{e^{-x} + e^x}{2} \\ &=& \cosh x \end{eqnarray}\)

これに三角関数の加法定理 [*1] を使うと次の式が導かれます。

\(\begin{eqnarray} \sin (x + iy) &=& \sin x \cos iy + \cos x \sin iy \\ &=& \sin x \cosh y + i \cos x \sinh y \\ \cos (x + iy) &=& \cos x \cos iy - \sin x \sin iy \\ &=& \cos x \cosh y - i \sin x \sinh y \\ \tan (x + iy) &=& \dfrac{\sin 2x + \sin 2iy}{cos 2x + cos 2iy} \\ &=& \dfrac{\sin 2x + i \sinh 2y}{\cos 2x + \cosh 2y} \end{eqnarray}\)

標準モジュール cmath にある三角関数 (sin, cos, tan) は複素数にも対応しています。簡単な実行例を示します。

>>> cmath.sin(1j)
1.1752011936438014j
>>> abs(cmath.sin(1j))
1.1752011936438014
>>> cmath.sin(0.0-1.0j)
-1.1752011936438014j
>>> abs(cmath.sin(0.0-1.0j))
1.1752011936438014
>>> cmath.sin(1.0 + 1.0j)
(1.2984575814159773+0.6349639147847361j)
>>> abs(cmath.sin(1.0 + 1.0j))
1.4453965766582495
>>> cmath.sin(1.0 - 1.0j)
(1.2984575814159773-0.6349639147847361j)
>>> abs(cmath.sin(1.0 - 1.0j))
1.4453965766582495

>>> cmath.cos(1j)
(1.5430806348152437-0j)
>>> abs(cmath.cos(1j))
1.5430806348152437
>>> cmath.cos(0.0-1.0j)
(1.5430806348152437+0j)
>>> abs(cmath.cos(0.0-1.0j))
1.5430806348152437
>>> cmath.cos(1.0+1.0j)
(0.8337300251311491-0.9888977057628651j)
>>> abs(cmath.cos(1.0+1.0j))
1.2934544550420957
>>> cmath.cos(1.0-1.0j)
(0.8337300251311491+0.9888977057628651j)
>>> abs(cmath.cos(1.0-1.0j))
1.2934544550420957

>>> cmath.tan(1j)
0.7615941559557649j
>>> abs(cmath.tan(1j))
0.7615941559557649
>>> cmath.tan(0.0-1.0j)
-0.7615941559557649j
>>> abs(cmath.tan(0.0-1.0j))
0.7615941559557649
>>> cmath.tan(1.0+1.0j)
(0.2717525853195118+1.0839233273386946j)
>>> abs(cmath.tan(1.0+1.0j))
1.1174700207060704
>>> cmath.tan(1.0-1.0j)
(0.2717525853195118-1.0839233273386946j)
>>> abs(cmath.tan(1.0-1.0j))
1.1174700207060704
-- note --------
[*1] 三角関数の公式は引数が複素数でも成り立ちます。ただし、\(|\sin x| \leq 1, |\cos x| \leq 1\) という関係式は、x が実数だと成立しますが複素数では成立しません。

●複素数の双曲線関数

複素数の双曲線関数の定義は、実数の定義で引数 x を複素数 z に変えたものになります。

双曲線関数の定義 (z は複素数)
\(\begin{eqnarray} \sinh z = \dfrac{e^{z} - e^{-z}}{2} \\ \cosh z = \dfrac{e^{z} + e^{-z}}{2} \end{eqnarray}\)

sinh z, cosh z に純虚数 ix を与えると三角関数 (sin x, cos x) になります。

\(\begin{eqnarray} \sinh ix &=& \dfrac{e^{ix} - e^{-ix}}{2} \\ &=& \dfrac{e^{ix} - e^{-ix}}{2} \times \dfrac{i}{i} \\ &=& i \dfrac{e^{ix} - e^{-ix}}{2i} \\ &=& i \sin x \\ \cosh ix &=& \dfrac{e^{ix} + e^{ix}}{2} \\ &=& cos x \end{eqnarray}\)

これに双曲線関数の加法定理を使うと、次の式が導かれます。

双曲線関数の加法定理
\(\begin{eqnarray} \sinh(x + y) &=& \sinh x \cosh y + \cosh x \sinh y \\ \cosh(x + y) &=& \cosh x \cosh y + \sinh x \sinh y \\ \tanh(x + y) &=& \dfrac{\sinh(x + y)}{\cosh(x + y)} \\ &=& \dfrac{\sinh 2x + \sinh 2y}{\cosh 2x + \cosh 2y} \end{eqnarray}\)
\(\begin{eqnarray} \sinh(x + iy) &=& \sinh x \cos y + i \cosh x \sin y \\ \cosh(x + iy) &=& \cosh x \cos y + i \sinh x \sin y \\ \tanh(x + iy) &=& \dfrac{\sinh(x + iy)}{\cosh(x + iy)} \\ &=& \dfrac{\sinh 2x + i \sin 2y}{\cosh 2x + \cos 2y} \end{eqnarray}\)

Python の標準モジュール cmath では双曲線関数 (sinh, cosh, tanh) を使用することができます。簡単な使用例を示します。

>>> cmath.sinh(1j)
0.8414709848078965j
>>> cmath.sinh(0.0-1.0j)
-0.8414709848078965j
>>> cmath.sinh(1.0+1.0j)
(0.6349639147847361+1.2984575814159773j)
>>> cmath.sinh(1.0)
(1.1752011936438014+0j)
>>> cmath.sinh(0.0)
0j

>>> cmath.cosh(1j)
(0.5403023058681398+0j)
>>> cmath.cosh(0.0-1.0j)
(0.5403023058681398-0j)
>>> cmath.cosh(1.0+1.0j)
(0.8337300251311491+0.9888977057628651j)
>>> cmath.cosh(1.0)
(1.5430806348152437+0j)
>>> cmath.cosh(0.0)
(1+0j)

>>> cmath.tanh(1j)
1.5574077246549023j
>>> cmath.tanh(0.0-1.0j)
-1.5574077246549023j
>>> cmath.tanh(1.0+1.0j)
(1.0839233273386946+0.2717525853195118j)
>>> cmath.tanh(1.0)
(0.7615941559557649+0j)
>>> cmath.tanh(0.0)
0j

●複素数の平方根

複素数 z の平方根は次の式で求めることができます。

\(z = x + iy, \ |z| = \sqrt{x^2 + y^2}, \ \theta = \arg z \ (-\pi \leq \theta \leq \pi)\) とすると

\( \sqrt{x + iy} = \begin{cases} \sqrt{|z| e^{i\theta}} = \sqrt{|z|} e^{i\theta/2} & (1) \\ \sqrt{|z| e^{i\theta + 2\pi}} = \sqrt{|z|} e^{i\theta/2 + \pi} & (2) \end{cases} \)

式 (1) を平方根の主値といいます。角度は \(2\pi\) を足すと同じ角度になるので、式 (2) がもう一つの解になります。三角関数の半角の公式を使うと、式 (1) から次の式が導かれます。

三角関数の半角の公式
\(\begin{eqnarray} \sin^2{\left(\frac{\theta}{2}\right)} = \dfrac{1 - \cos \theta}{2} \\ \cos^2{\left(\frac{\theta}{2}\right)} = \dfrac{1 + \cos \theta}{2} \end{eqnarray}\)
\(y \geq 0\) の場合
\(\begin{eqnarray} \sqrt{|z|} e^{i\theta/2} &=& \sqrt{|z|} \left(\cos{\frac{\theta}{2}} + i \sin{\frac{\theta}{2}}\right) \\ &=& \sqrt{|z|} \left(\sqrt{\frac{1 + \cos \theta}{2}}) + i \sqrt{\frac{1 - \cos \theta}{2}}\right) \\ &=& \dfrac{\sqrt{|z| + |z|\cos \theta}}{2} + i \sqrt{\frac{|z| - |z| \cos \theta}{2}} \\ &=& \sqrt{\frac{|z| + x}{2}} + i \sqrt{\frac{|z| - x}{2}}, \quad (|z|\cos \theta = x) \end{eqnarray}\)

\(y \lt 0\) の場合、虚部の符号が \(-\) になる
\( \sqrt{|z|} e^{i\theta/2} = \sqrt{\dfrac{|z| + x}{2}} - i \sqrt{\dfrac{|z| - x}{2}} \)

Python の標準モジュール cmath にある sqrt は複素数にも対応しています。簡単な実行例を示します。

>>> cmath.sqrt(1.0)
(1+0j)
>>> cmath.sqrt(2.0)
(1.4142135623730951+0j)
>>> cmath.sqrt(3.0)
(1.7320508075688772+0j)
>>> cmath.sqrt(-1.0)
1j
>>> cmath.sqrt(-2.0)
1.4142135623730951j
>>> cmath.sqrt(-3.0)
1.7320508075688772j

>>> a = cmath.sqrt(1.0+1.0j)
>>> a
(1.09868411346781+0.45508986056222733j)
>>> a * a
(1.0000000000000002+1j)

>>> a = cmath.sqrt(1.0-1.0j)
>>> a
(1.09868411346781-0.45508986056222733j)
>>> a * a
(1.0000000000000002-1j)

>>> a = cmath.sqrt(-1.0+1.0j)
>>> a
(0.45508986056222733+1.09868411346781j)
>>> a * a
(-1.0000000000000002+1j)

>>> a = cmath.sqrt(-1.0-1.0j)
>>> a
(0.45508986056222733-1.09868411346781j)
>>> a * a
(-1.0000000000000002-1j)

\(\sqrt x\) は \(\log x\) と同じ分枝切断を持っています。x を負の整数とすると sqrt(x) の解は \(i \sqrt x\) になりますが、もうひとつ \(-i \sqrt x\) という解があります。

>>> cmath.sqrt(complex(-1, 0.0))
1j
>>> cmath.sqrt(complex(-1, -0.0))
-1j
>>> cmath.sqrt(complex(-2, 0.0))
1.4142135623730951j
>>> cmath.sqrt(complex(-2, -0.0))
-1.4142135623730951j
>>> cmath.sqrt(complex(-1e300, 0.0))
1e+150j
>>> cmath.sqrt(complex(-1e300, -0.0))
-1e+150j

●逆三角関数

三角関数の逆関数を「逆三角関数 (inverse trigonometric function)」といいます。以下に Python の標準モジュール math にある逆三角関数を示します。

ここでは引数 x を実数とします。asin x は引数 x が与えられたとき sin w = x となる角度 w を求めます。同様に acos x は cos w = x となる角度 w を、atan x は tan x = w となる角度 w を求めます。三角関数には周期性があるので、上式を満たす角度 w は無数に存在します。つまり、逆三角関数の返り値は無数にあることになりますが、通常は一つの値を返すように範囲を制限します。これを「主値」といいます。

逆三角関数の主値を以下に示します。

\(\begin{array}{lcc} \arcsin x = w & -1 \leq x \lt 1 & -\pi/2 \leq w \leq \pi/2 \\ \arccos x = w & -1 \leq x \lt 1 & 0 \leq w \leq \pi \\ \arctan x = w & x \in \mathbb{R} & -\pi/2 \leq w \leq \pi/2 \end{array}\)

本ページでは、数式の表示に JavaScript のライブラリ MathJax を使っています。MathJax では、逆三角関数を arcsin, arccos, arctan と表示します。

簡単な実行例を示します。

>>> for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: print(math.asin(x))
...
-1.5707963267948966
-0.5235987755982989
0.0
0.5235987755982989
1.5707963267948966
>>> for x in [-1.0, -0.5, 0.0, 0.5, 1.0]: print(math.acos(x))
...
3.141592653589793
2.0943951023931957
1.5707963267948966
1.0471975511965979
0.0
>>> for x in [-1e300, -1.0, 0.0, 1.0, 1e300]: print(math.atan(x))
...
-1.5707963267948966
-0.7853981633974483
0.0
0.7853981633974483
1.5707963267948966

標準モジュール math には 2 引数の関数 atan2 も用意されています。これは他のプログラミング言語、たとえばC言語の数学関数 atan2 と同じです。

atan2(y, x) => 角度 (ラジアン)

引数 x, y は実数です。atan2 は直交座標系においてベクトル (x, y) と x 軸との角度を求める関数です。複素平面で考えると、複素数 \(x + iy\) の偏角 \(\theta\) を求めることと同じです。返り値 (角度 \(\theta\)) の範囲は \(-\pi \leq \theta \leq \pi\) になります。

簡単な実行例を示します。

>>> math.atan2(0.0, 1.0)
0.0
>>> math.atan2(1.0, 1.0)
0.7853981633974483
>>> math.atan2(1.0, 0.0)
1.5707963267948966
>>> math.atan2(1.0, -1.0)
2.356194490192345
>>> math.atan2(0.0, -1.0)
3.141592653589793
>>> math.atan2(-1.0, 1.0)
-0.7853981633974483
>>> math.atan2(-1.0, 0.0)
-1.5707963267948966
>>> math.atan2(-1.0, -1.0)
-2.356194490192345
>>> math.atan2(-0.0, -1.0)
-3.141592653589793

●複素数の逆三角関数

複素数の逆三角関数の定義は、複素数の三角関数の定義から導くことができます。\(\arcsin z\) の定義は次のようになります。

\(z, w\) は複素数とする

\(\begin{array}{l} \arcsin z = w \\ z = \sin w = \dfrac{e^{iw} - e^{-iw}}{2i} \\ z \times 2ie^{iw} = \dfrac{e^{iw} - e^{-iw}}{2i} \times 2ie^{iw} \\ 2iz(e^{iw}) = (e^{iw})^2 - 1 \\ (e^{iw})^2 - 2iz(e^{iw}) - 1 = 0 \end{array}\)

\(e^{iw}\) の二次方程式と考えて解くと

\(e^{iw} = iz \pm \sqrt{1 - z^2}\)

両辺の対数をとって \(-i\) を掛け算し、平方根の主値 \(+\) を選ぶ

\(w = \arcsin z = -i \log{\left(iz + \sqrt{1 - z^2}\right)} \)

\(\arccos z, \arctan z\) は定義だけを示します。

\(\begin{array}{l} \arccos z = \dfrac{\pi}{2} - \arcsin z \\ \arctan z = i \dfrac{\log{\left(1 - iz\right)} - \log{\left(1 + iz\right)}}{2} \end{array}\)

asin, acos, atan は次に示す分枝切断を持っています。

Python の標準モジュール cmath の逆三角関数 (asin, acos, atan) は複素数に対応しています。簡単な実行例を示します。

>>> for x in [-1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0]: print(cmath.asin(x))
...
(-1.5707963267948966+0j)
(-0.848062078981481+0j)
(-0.5235987755982989+0j)
(-0.25268025514207865+0j)
0j
(0.25268025514207865+0j)
(0.5235987755982989+0j)
(0.848062078981481+0j)
(1.5707963267948966+0j)
>>> cmath.asin(+1j)
0.881373587019543j
>>> cmath.sin(cmath.asin(+1j))
1j
>>> cmath.asin(1.0+1.0j)
(0.6662394324925153+1.0612750619050357j)
>>> cmath.sin(cmath.asin(1.0+1.0j))
(1.0000000000000002+1j)

>>> cmath.asin(complex(2.0, 0.0))
(1.5707963267948966+1.3169578969248166j)
>>> cmath.asin(complex(2.0, -0.0))
(1.5707963267948966-1.3169578969248166j)
>>> cmath.asin(complex(-4.0, 0.0))
(-1.5707963267948966+2.0634370688955608j)
>>> cmath.asin(complex(-4.0, -0.0))
(-1.5707963267948966-2.0634370688955608j)

>>> for x in [-1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0]: print(cmath.acos(x))
...
(3.141592653589793-0j)
(2.4188584057763776-0j)
(2.0943951023931953-0j)
(1.8234765819369754-0j)
(1.5707963267948966-0j)
(1.318116071652818-0j)
(1.0471975511965979-0j)
(0.7227342478134156-0j)
-0j
>>> cmath.acos(1j)
(1.5707963267948966-0.881373587019543j)
>>> cmath.cos(cmath.acos(1j))
(8.659560562354932e-17+1j)
>>> cmath.acos(1.0+1.0j)
(0.9045568943023814-1.0612750619050357j)
>>> cmath.cos(cmath.acos(1.0+1.0j))
(0.9999999999999999+1j)

>>> cmath.acos(complex(2.0, 0.0))
-1.3169578969248166j
>>> cmath.acos(complex(2.0, -0.0))
1.3169578969248166j
>>> cmath.acos(complex(-4.0, 0.0))
(3.141592653589793-2.0634370688955608j)
>>> cmath.acos(complex(-4.0, -0.0))
(3.141592653589793+2.0634370688955608j)

>>> for z in [2.0, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5, -2.0]: print(cmath.atan(z))
...
(1.1071487177940904+0j)
(0.982793723247329+0j)
(0.7853981633974483+0j)
(0.4636476090008061+0j)
0j
(-0.4636476090008061+0j)
(-0.7853981633974483+0j)
(-0.982793723247329+0j)
(-1.1071487177940904+0j)
>>> cmath.atan(1.0+1.0j)
(1.0172219678978514+0.40235947810852507j)
>>> cmath.tan(cmath.atan(1.0+1.0j))
(1.0000000000000002+1j)
>>> cmath.atan(1.0-1.0j)
(1.0172219678978514-0.40235947810852507j)
>>> cmath.tan(cmath.atan(1.0-1.0j))
(1.0000000000000002-1j)

>>> cmath.atan(complex(0.0, 2.0))
(1.5707963267948966+0.5493061443340549j)
>>> cmath.atan(complex(-0.0, 2.0))
(-1.5707963267948966+0.5493061443340549j)
>>> cmath.atan(complex(0.0, -4.0))
(1.5707963267948966-0.25541281188299536j)
>>> cmath.atan(complex(-0.0, -4.0))
(-1.5707963267948966-0.25541281188299536j)

●逆双曲線関数

双曲線関数の逆関数を「逆双曲線関数 (inverse hyperbolic function)」といいます。Python の標準モジュール math で用意されている逆双曲線関数には asinh, acosh, atanh があります。MathJax では逆双曲線関数を \(\sinh^{-1} x, \cosh^{-1} x, \tanh^{-1} x \) で表します。双曲線関数と逆双曲線関数の定義域と値域を示します。

\(\begin{array}{lcc} y = \sinh x & -\infty \lt x \lt \infty & -\infty \lt y \lt \infty \\ y = \cosh x & -\infty \lt x \lt \infty & 1 \leq y \lt \infty \\ y = \tanh x & -\infty \lt x \lt \infty & -1 \lt y \lt 1 \\ y = \sinh^{-1} x & -\infty \lt x \lt \infty & -\infty \lt y \lt \infty \\ y = \cosh^{-1} x & 1 \leq x \lt \infty & 0 \leq y \lt \infty \\ y = \tanh^{-1} x & -1 \lt x \lt 1 & -\infty \lt y \lt \infty \end{array}\)

x と y は実数です。x = cosh y の逆関数 y = acosh x を満たす y の値は 2 つありますが、ここでは \(y \geq 0\) を主値として選ぶことにします。

逆双曲線関数の定義は双曲線関数の定義から導くことができます。

\(\begin{array}{l} \sinh^{-1} x = y \\ y = \sinh x = \dfrac{e^{x} - e^{-x}}{2} \\ y \times 2e^{x} = \dfrac{e^{x} - e^{-x}}{2} \times 2e^{x} \\ 2ye^{x} = e^{2x} - 1 \\ e^{2x} - 2ye^{x} - 1 = 0 \end{array}\)

\(e^{x}\) の 2 次方程式として解く

\(e^{x} = \dfrac{2y \pm \sqrt{4y^2 + 4}}{2} = y \pm \sqrt{y^2 + 1} \)

\(e^{x} \gt 0\) だから平方根の符号に \(+\) を選んで対数をとる

\(x = \log \left(y + \sqrt{y^2 + 1}\right)\)
\(\begin{array}{l} \cosh^{-1} x = y \\ y = \cosh x = \dfrac{e^{x} + e^{-x}}{2} \\ y \times 2e^{x} = \dfrac{e^{x} + e^{-x}}{2} \times 2e^{x} \\ 2ye^{x} = e^{2x} + 1 \\ e^{2x} - 2ye^{x} + 1 = 0 \end{array}\)

\(e^{x}\) の 2 次方程式として解く

\(e^{x} = \dfrac{2y \pm \sqrt{\left(4y^2 - 4\right)}}{2} = y \pm \sqrt{y^2 - 1} \)

\(e^{x} \gt 0\) だから平方根の符号に \(+\) を選んで対数をとる

\(x = \log \left(y + \sqrt{y^2 - 1}\right), \quad (y \geq 1) \)
\(\begin{array}{l} \tanh^{-1} x = y \\ \begin{eqnarray} y &=& \tanh x \\ &=& \dfrac{\sinh x}{\cosh x} \\ &=& \dfrac{e^{x} - e^{-x}}{e^{x} + e^{-x}} \\ &=& \dfrac{e^{2x} - 1}{e^{2x} + 1} \end{eqnarray} \\ y(e^{2x} + 1) = (e^{2x} - 1) \\ (1 - y)e^{2x} = 1 + y \\ e^{2x} = \dfrac{1 + y}{1 - y} \\ x = \dfrac{1}{2} \log{\dfrac{1 + y}{1 - y}} \end{array}\)

関数 \(\tanh^{-1} x\) を使うと、次の関係式から \(\sinh^{-1} x, \ \cosh^{-1} x\) を求めることができます。

\(\sinh^{-1} x = \tanh^{-1}{\dfrac{x}{\sqrt{x^2 + 1}}} \)

\(\cosh^{-1} x = \tanh^{-1}{\dfrac{\sqrt{1 - x^2}}{x}} \)

簡単な実行例を示します。

>>> for x in [2.0, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5, -2.0]: print(math.asinh(x))
...
1.4436354751788103
1.1947632172871094
0.881373587019543
0.48121182505960347
0.0
-0.48121182505960347
-0.881373587019543
-1.1947632172871094
-1.4436354751788103
>>> for x in [2.0, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5, -2.0]: print(math.sinh(math.asinh(x)))
...
1.9999999999999998
1.5000000000000004
1.0
0.5
0.0
-0.5
-1.0
-1.5000000000000004
-1.9999999999999998

>>> for x in [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]: print(math.acosh(x))
...
0.0
0.9624236501192069
1.3169578969248166
1.566799236972411
1.762747174039086
1.9248473002384139
2.0634370688955608
>>> for x in [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]: print(math.cosh(math.acosh(x)))
...
1.0
1.5
1.9999999999999998
2.5
3.0000000000000004
3.5000000000000004
4.000000000000001

>>> for x in [-0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75]: print(math.atanh(x))
...
-0.9729550745276566
-0.5493061443340548
-0.25541281188299536
0.0
0.25541281188299536
0.5493061443340548
0.9729550745276566
>>> for x in [-0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75]: print(math.tanh(math.atanh(x)))
...
-0.75
-0.49999999999999994
-0.25
0.0
0.25
0.49999999999999994
0.75

●複素数の逆双曲線関数

複素数の逆双曲線関数の定義は、実数の定義で引数 x を複素数 z に変えたものになります。

\(\begin{array}{l} \sinh^{-1} z = \log \left(z + \sqrt{z^2 + 1}\right) \\ \begin{eqnarray} \cosh^{-1} z &=& \log \left(z + \sqrt{z^2 - 1}\right) &(1)& \\ &=& \log \left(z + \sqrt{z + 1} \sqrt{z - 1}\right) \quad &(2)& \end{eqnarray} \\ \tanh^{-1} z = \dfrac{1}{2} \log\left(\dfrac{1 + z}{1 - z}\right) = \dfrac{1}{2} \left( \log {\left( 1 + z \right)} - \log {\left(1 - z\right)} \right) \end{array}\)

参考文献, URL 7 によると、\(\cosh^{-1} z\) を式 (1) でプログラムすると「分枝切断線」が複雑になるため、他の式 (たとえば (2) など) でプログラムする処理系が多いようです。ちなみに、ANSI Common Lisp では \(\cosh^{-1} z\) を次の式で定義しています。

\( \cosh^{-1} z = 2 \log \left( \sqrt{\dfrac{z + 1}{2}} + \sqrt{\dfrac{z - 1}{2}} \right) \)

asinh, acosh, atanh は次に示す分枝切断を持っています。

Python の標準モジュール cmath の逆双曲線関数 (asinh, acosh, atanh) は複素数に対応しています。簡単な実行例を示します。

>>> for x in [2.0, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5, -2.0]: print(cmath.asinh(complex(x, 0.0)))
...
(1.4436354751788103+0j)
(1.1947632172871094+0j)
(0.881373587019543+0j)
(0.48121182505960347+0j)
0j
(-0.48121182505960347+0j)
(-0.881373587019543+0j)
(-1.1947632172871094+0j)
(-1.4436354751788103+0j)
>>> for x in [2.0, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5, -2.0]: print(cmath.sinh(cmath.asinh(complex(x, 0.0))))
...
(1.9999999999999998+0j)
(1.5000000000000004+0j)
(1+0j)
(0.5+0j)
0j
(-0.5+0j)
(-1+0j)
(-1.5000000000000004+0j)
(-1.9999999999999998+0j)
>>> cmath.asinh(1.0+1.0j)
(1.0612750619050357+0.6662394324925153j)
>>> cmath.sinh(cmath.asinh(1.0+1.0j))
(1+1.0000000000000002j)

>>> cmath.asinh(complex(0.0, 2.0))
(1.3169578969248166+1.5707963267948966j)
>>> cmath.asinh(complex(-0.0, 2.0))
(-1.3169578969248166+1.5707963267948966j)
>>> cmath.asinh(complex(0.0, -4.0))
(2.0634370688955608-1.5707963267948966j)
>>> cmath.asinh(complex(-0.0, -4.0))
(-2.0634370688955608-1.5707963267948966j)

>>> for x in [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]: print(cmath.acosh(x))
...
0j
(0.9624236501192069+0j)
(1.3169578969248166+0j)
(1.5667992369724109+0j)
(1.762747174039086+0j)
(1.9248473002384137+0j)
(2.0634370688955608+0j)
>>> for x in [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]: print(cmath.cosh(cmath.acosh(x)))
...
(1+0j)
(1.5+0j)
(1.9999999999999998+0j)
(2.499999999999999+0j)
(3.0000000000000004+0j)
(3.4999999999999996+0j)
(4.000000000000001+0j)
>>> cmath.acosh(1.0+1.0j)
(1.0612750619050357+0.9045568943023813j)
>>> cmath.cosh(cmath.acosh(1.0+1.0j))
(1.0000000000000002+1j)

>>> cmath.acosh(complex(0.0, 0.0))
1.5707963267948966j
>>> cmath.acosh(complex(0.0, -0.0))
-1.5707963267948966j
>>> cmath.acosh(complex(-4.0, 0.0))
(2.0634370688955608+3.141592653589793j)
>>> cmath.acosh(complex(-4.0, -0.0))
(2.0634370688955608-3.141592653589793j)

>>> for x in [-0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75]: print(cmath.atanh(x))
...
(-0.9729550745276566+0j)
(-0.5493061443340549+0j)
(-0.25541281188299536+0j)
0j
(0.25541281188299536+0j)
(0.5493061443340549+0j)
(0.9729550745276566+0j)
>>> for x in [-0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75]: print(cmath.tanh(cmath.atanh(x)))
...
(-0.75+0j)
(-0.5000000000000001+0j)
(-0.25+0j)
0j
(0.25+0j)
(0.5000000000000001+0j)
(0.75+0j)
>>> cmath.atanh(1.0+1.0j)
(0.40235947810852507+1.0172219678978514j)
>>> cmath.tanh(cmath.atanh(1.0+1.0j))
(1+1.0000000000000002j)

>>> cmath.atanh(complex(2.0, 0.0))
(0.5493061443340549+1.5707963267948966j)
>>> cmath.atanh(complex(2.0, -0.0))
(0.5493061443340549-1.5707963267948966j)
>>> cmath.atanh(complex(-4.0, 0.0))
(-0.25541281188299536+1.5707963267948966j)
>>> cmath.atanh(complex(-4.0, -0.0))
(-0.25541281188299536-1.5707963267948966j)

●参考文献, URL

  1. 奥村晴彦,『C言語による最新アルゴリズム事典』, 技術評論社, 1991
  2. IEEE 754 -- Wikipedia
  3. IEEE 754における負のゼロ - Wikipedia
  4. NaN - Wikipedia
  5. 逆三角関数 - Wikipedia
  6. 逆双曲線関数 - Wikipedia
  7. 逆双曲線関数と逆三角関数の branch cut | 雑記帳

Copyright (C) 2021 Makoto Hiroi
All rights reserved.

[ Home | Light | Python3 ]