M.Hiroi's Home Page

Functional Programming

お気楽 Haskell プログラミング入門

[ PrevPage | Haskell | NextPage ]

複素数

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

他の言語では、FORTRAN や Common Lisp が昔から複素数型をサポートしています。Common Lisp の場合、基本的な数学関数でも複素数を適用できるのであれば、引数に複素数を渡して計算することができます。Haskell の場合、モジュール Data.Complex をインポートすると、複素数を使用することができます。今回は Haskell の複素数についてまとめてみました。

●Haskell の数

Haskell の数はプリミティブな型で大きく分けると、整数 (Int, Integer) と実数 (Float, Double) の 2 種類があります。Haskell の Integer は多倍長整数なので、Integer を使えばメモリの許す限り任意の精度で扱うことができます。モジュール Data..Ratio をインポートすると有理数 (分数) を使うことができます。特に、Rational は分子と分母の型が Integer なので、メモリの許す限り任意の精度で扱うことができます。

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

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

●無限大

一般に、無限大は値のオーバーフロー、ゼロ除算 (数値 / 0.0)、数学関数の計算結果 (たとえば log(0.0)) などで発生します。なお、浮動小数点数のゼロ除算でエラー (例外) を送出する処理系 (たとえば Python など) もあります。

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

Prelude> 1e308
1.0e308
Prelude> 1e309
Infinity
Prelude> 1e308 * 2
Infinity
Prelude> -1e308
-1.0e308
Prelude> -1e309
-Infinity
Prelude> -1e308 * 2
-Infinity
Prelude> 1 / 0
Infinity
Prelude> -1 / 0
-Infinity
Prelude> log(0)
-Infinity

Haskell の場合、無限大の判定は関数 isInfinite を使います。

Prelude> isInfinite (1 / 0)
True
Prelude> isInfinite (-1 / 0)
True
Prelude> isInfinite 0
False
Prelude> isInfinite 1.2345
False

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

Prelude> a = 1 / 0
Prelude> a
Infinity
Prelude> b = -1 / 0
Prelude> b
-Infinity
Prelude> a > b
True
Prelude> a < b
False
Prelude> a == b
False
Prelude> a /= b
True
Prelude> a > 0
True
Prelude> a < 0
False
Prelude> b < 0
True
Prelude> b > 0
False
Prelude> a + 100
Infinity
Prelude> a - 100
Infinity
Prelude> a * 100
Infinity
Prelude> a / 100
Infinity
Prelude> a + a
Infinity
Prelude> a * a
Infinity
Prelude> a - a
NaN
Prelude> a / a
NaN
Prelude> a + b
NaN
Prelude> a * 0
NaN

●負のゼロ

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

Prelude> -1e-323
-1.0e-323
Prelude> -1e-324
-0.0
Prelude> -1e-323 / 2
-5.0e-324
Prelude> -1e-323 / 4
-0.0
Prelude> -1.0 / (1 / 0)
-0.0
Prelude> 1.0 / (-1 / 0)
-0.0
Prelude> -1.0 * 0.0
-0.0

標準 (IEEE 754) では、演算子 (== など) による 0.0 と -0.0 の比較は等しいと判定されます。Haskell の場合、-0.0 は関数 isNegativeZero で判別することができます。また、任意の正の数を -0.0 で除算すると -Infinity になるので、それを使って判別することもできます。

Prelude> a = 0.0
Prelude> b = -0.0
Prelude> a
0.0
Prelude> b
-0.0
Prelude> a == b
True
Prelude> isNegativeZero a
False
Prelude> isNegativeZero b
True
Prelude> 1 / a
Infinity
Prelude> 1 / b
-Infinity

-0.0 は数学関数 (たとえば atan2) や複素数の演算処理などで使われます。

Prelude> atan2 0 (-1)
3.141592653589793
Prelude> atan2 (-0) (-1)
-3.141592653589793
Prelude> sqrt(0)
0.0
Prelude> sqrt(-0)
-0.0

●非数

NaN は数ではないことを表す特別な値 (非数) です。一般的には 0.0 / 0.0 といった不正な演算を行うと、その結果は NaN になります。たとえば、負数の平方根は虚数になりますが、関数 sqrt に負数を与えると浮動小数点数では表現できないので NaN になります。

Prelude> 0 / 0
NaN
Prelude> sqrt(-1)
NaN
Prelude> a = (1 / 0)
Prelude> a
Infinity
Prelude> a / a
NaN
Prelude> isNaN (a/a)
True
Prelude> isNaN (-0)
False

Haskell の場合、NaN は関数 isNaN で判別することができます。

●Haskell の複素数

数学では複素数 z を x + yi と表記します。x を実部、y を虚部、i を虚数単位といいます。虚数単位は 2 乗すると -1 になる数です。実部と虚部の 2 つの数値を格納するデータ構造を用意すれば、プログラミング言語でも複素数を表すことができます。Haskell で複素数を使用するときはモジュール Data.Complex をインポートしてください。複素数の型は Complex a になります。

data Complex a = !a :+ !a

Haskell は複素数 x + yi を x :+ y と記述します。演算子 :+ は複素数を生成します。複素数 z の実部は関数 realPart で、虚部は関数 imagPart で取得することができます。複素数の虚部の符号を反転することを「複素共役」といいます。複素共役は関数 conjugate で求めることができます。

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

Prelude> :m + Data.Complex
Prelude Data.Complex> 1 :+ 2
1 :+ 2
Prelude Data.Complex> :t 1 :+ 2
1 :+ 2 :: Num a => Complex a
Prelude Data.Complex> 1.0 :+ 2.0
1.0 :+ 2.0
Prelude Data.Complex> :t 1.0 :+ 2.0
1.0 :+ 2.0 :: Fractional a => Complex a
Prelude Data.Complex> a = 1.234 :+ 5.678
Prelude Data.Complex> a
1.234 :+ 5.678
Prelude Data.Complex> realPart a
1.234
Prelude Data.Complex> imagPart a
5.678
Prelude Data.Complex> conjugate a
1.234 :+ (-5.678)

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

極形式 z = r (cos θ + i sin θ) で複素数を生成するときは関数 mkPolar を使います。

mkPolar :: Floating a => a -> a -> Complex a

第 1 引数が絶対値 r、第 2 引数が偏角 θ を表します。複素数 z はオイラーの公式 e = cos θ + i sin θ を使って z = r e と書くこともできます。ここで、θ に pi を代入するとオイラーの等式 ei pi + 1 = 0 になります。また、下記のようにド・モアヴルの公式も導出することができます。

zn = (re)n = rneinθ = rn(cos nθ + i sin nθ)

関数 cis x は絶対値が 1 で偏角が x の複素数を生成します。cis は cos + i sin の略です。関数 polar は複素数の絶対値と偏角を求めます。

cis :: Floating a => a -> Complex a
polar :: RealFloat a => Complex a -> (a, a)

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

Prelude Data.Complex> a = 1.0 :+ 1.0
Prelude Data.Complex> a
1.0 :+ 1.0
Prelude Data.Complex> magnitude a
1.4142135623730951
Prelude Data.Complex> phase a
0.7853981633974483
Prelude Data.Complex> mkPolar 1.4142135623730951 0.7853981633974483
1.0000000000000002 :+ 1.0
Prelude Data.Complex> magnitude (cis 0.7853981633974483)
1.0
Prelude Data.Complex> polar a
(1.4142135623730951,0.7853981633974483)


Prelude Data.Complex> phase ((-1.0) :+ 0.0)
3.141592653589793
Prelude Data.Complex> phase ((-1.0) :+ 1.0)
2.356194490192345
Prelude Data.Complex> phase (0.0 :+ 1.0)
1.5707963267948966
Prelude Data.Complex> phase (1.0 :+ 1.0)
0.7853981633974483
Prelude Data.Complex> phase (1.0 :+ 0.0)
0.0
Prelude Data.Complex> phase (1.0 :+ (-1.0))
-0.7853981633974483
Prelude Data.Complex> phase (0.0 :+ (-1.0))
-1.5707963267948966
Prelude Data.Complex> phase ((-1.0) :+ (-1.0))
-2.356194490192345
Prelude Data.Complex> phase ((-1.0) :+ (-0.0))
-3.141592653589793

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

●複素数の四則演算

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

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

Prelude Data.Complex> a = 1.0 :+ 2.0
Prelude Data.Complex> a
1.0 :+ 2.0
Prelude Data.Complex> b = 3.0 :+ 4.0
Prelude Data.Complex> b
3.0 :+ 4.0
Prelude Data.Complex> a + b
4.0 :+ 6.0
Prelude Data.Complex> a - b
(-2.0) :+ (-2.0)
Prelude Data.Complex> a * b
(-5.0) :+ 10.0
Prelude Data.Complex> a / b
0.44 :+ 8.0e-2
Prelude Data.Complex> a == a
True
Prelude Data.Complex> a == b
False
Prelude Data.Complex> a /= a
False
Prelude Data.Complex> a /= b
True

実部と虚部の値は Infinity や NaN になることもあります。

Prelude Data.Complex> c = 1e300 :+ 1e300
Prelude Data.Complex> c
1.0e300 :+ 1.0e300
Prelude Data.Complex> c * c
NaN :+ Infinity

●複素関数

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

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

e = cos θ + i sin θ -- オイラーの公式
ex+iy = ex * eiy = ex * (cos y + i sin y)

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

x + iy = |z| * e
loge (x + iy) = loge (|z| * e) 
              = loge |z| + loge e
              = loge |z| + iθ, (-pi <= θ <= pi)

複素数 x, y のべき乗 xy は ey*log x で求めることができます。

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

Prelude Data.Complex> pi
3.141592653589793
Prelude Data.Complex> exp (0.0 :+ (pi / 4))
0.7071067811865476 :+ 0.7071067811865475
Prelude Data.Complex> exp (0.0 :+ (pi / 2))
6.123233995736766e-17 :+ 1.0
Prelude Data.Complex> exp (0.0 :+ pi)
(-1.0) :+ 1.2246467991473532e-16
Prelude Data.Complex> exp (1.0 :+ 1.0)
1.4686939399158851 :+ 2.2873552871788423


Prelude Data.Complex> log (1.0 :+ 1.0)
0.3465735902799727 :+ 0.7853981633974483
Prelude Data.Complex> log (1.0 :+ 0.0)
0.0 :+ 0.0
Prelude Data.Complex> log (0.0 :+ 1.0)
0.0 :+ 1.5707963267948966
Prelude Data.Complex> log (1.0 :+ (-1.0))
0.3465735902799727 :+ (-0.7853981633974483)
Prelude Data.Complex> log (1e300 :+ 1e300)
691.1221014884936 :+ 0.7853981633974483


Prelude Data.Complex> (1.0 :+ 1.0) ** 0
1.0 :+ 0.0
Prelude Data.Complex> (1.0 :+ 1.0) ** 1
1.0000000000000002 :+ 1.0
Prelude Data.Complex> (1.0 :+ 1.0) ** 2
1.2246467991473532e-16 :+ 2.0
Prelude Data.Complex> (1.0 :+ 1.0) ** 3
(-2.0) :+ 2.0000000000000004
Prelude Data.Complex> (1.0 :+ 1.0) ** (1.0 :+ 1.0)
0.2739572538301211 :+ 0.5837007587586147
Prelude Data.Complex> (1.0 :+ 2.0) ** (3.0 :+ 4.0)
0.129009594074467 :+ 3.392409290517014e-2

関数 log z (z = x + yi) は負の実軸 (-∞ < x < 0) において、x + 0.0i と x - 0.0i では値が異なります。

Prelude Data.Complex> log ((-1.0) :+ 0.0)
0.0 :+ 3.141592653589793
Prelude Data.Complex> log ((-1.0) :+ (-0.0))
0.0 :+ (-3.141592653589793)
Prelude Data.Complex> log ((-1e300) :+ 0.0)
690.7755278982137 :+ 3.141592653589793
Prelude Data.Complex> log ((-1e300) :+ (-0.0))
690.7755278982137 :+ (-3.141592653589793)

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

参考 URL 8 によると、この取り除いた領域を branch cut と呼ぶそうです。プログラミングでは branch cut を定義域から取り除くのではなく、その領域では不連続な関数とするそうです。参考文献 1 では「分枝切断線」、Python のドキュメントでは「分枝切断」と記述されています。本稿では branch cut を「分枝切断」と記述することにします。

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

●複素数の三角関数

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

e = cos θ + i sin θ  -- (1)
ei(-θ) = cos θ + i sin -θ  (sin -θ = -sin θ, cos -θ = cos θ)
      = cos θ - i sin θ -- (2)
(1) + (2) 
e + e-iθ = 2 cos θ
cos θ = (e + e-iθ) / 2
(1) - (2) 
e - e-iθ = 2i sin θ
sin θ = (e - e-iθ) / 2i

θ を複素数 z に置き換えた式が三角関数の定義になる
sin z = (eiz - e-iz) / 2i
cos z = (eiz + e-iz) / 2

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

双曲線関数の定義
sinh x = (ex - e-x) / 2 
cosh x = (ex + e-x) / 2

sin ix = (eiix - e-iix) / 2i
       = (e-x - ex) / 2i  (分子と分母に -i を掛ける)
       = i (ex - e-x) / 2
       = i sinh x
cos ix = (eiix + e-iix) / 2
       = (e-x + ex) / 2
       = cosh x

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

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) = (sin 2x + sin 2iy) / (cos 2x + cos 2iy)
            = (sin 2x + i sinh 2y) / (cos 2x + cosh 2y) 

Haskell の三角関数 (sin, cos, tan) は複素数にも対応しています。簡単な実行例を示します。

Prelude Data.Complex> a = [0.0 :+ 1.0, 0.0 :+ (-1.0), 1.0 :+ 1.0, 1.0 :+ (-1.0)]
Prelude Data.Complex> a
[0.0 :+ 1.0,0.0 :+ (-1.0),1.0 :+ 1.0,1.0 :+ (-1.0)]

Prelude Data.Complex> map sin a
[0.0 :+ 1.1752011936438014,
 0.0 :+ (-1.1752011936438014),
 1.2984575814159773 :+ 0.6349639147847361,
 1.2984575814159773 :+ (-0.6349639147847361)]
Prelude Data.Complex> map (magnitude . sin) a
[1.1752011936438014,1.1752011936438014,1.4453965766582495,1.4453965766582495]

Prelude Data.Complex> map cos a
[1.5430806348152437 :+ (-0.0),
 1.5430806348152437 :+ 0.0,
 0.8337300251311491 :+ (-0.9888977057628651),
 0.8337300251311491 :+ 0.9888977057628651]
Prelude Data.Complex> map (magnitude . cos) a
[1.5430806348152437,1.5430806348152437,1.2934544550420957,1.2934544550420957]

Prelude Data.Complex> map tan a
[0.0 :+ 0.7615941559557649,
 0.0 :+ (-0.7615941559557649),
 0.2717525853195117 :+ 1.0839233273386946,
 0.2717525853195117 :+ (-1.0839233273386946)]
Prelude Data.Complex> map (magnitude . tan) a
[0.7615941559557649,0.7615941559557649,1.1174700207060704,1.1174700207060704]
-- note --------
[*1] 三角関数の公式は引数が複素数でも成り立ちます。ただし、|sin x| <= 1, |cos x| <= 1 という関係式は、x が実数だと成立しますが複素数では成立しません。

●複素数の双曲線関数

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

双曲線関数の定義 (z は複素数)
sinh z = (ez - e-z) / 2 
cosh z = (ez + e-z) / 2

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

sinh ix = (eix - e-ix) / 2
        = i (eix - e-ix) / 2i  (分子と分母に i を掛ける)
        = i sin x
cosh ix = (eix + eix) / 2
        = cos x

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

双曲線関数の加法定理
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) = sinh(x + y) / cosh(x + y)
            = (sinh 2x + sinh 2y) / (cosh 2x + cosh 2y)

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) = sinh(x + iy) / cosh(x + iy)
             = (sinh 2x + i sin 2y) / (cosh 2x + cos 2y)

Haskell の双曲線関数 (sinh, cosh, tanh) は複素数に対応しています。簡単な使用例を示します。

Prelude Data.Complex> a
[0.0 :+ 1.0,0.0 :+ (-1.0),1.0 :+ 1.0,1.0 :+ (-1.0)]

Prelude Data.Complex> map sinh a
[0.0 :+ 0.8414709848078965,
 0.0 :+ (-0.8414709848078965),
 0.6349639147847361 :+ 1.2984575814159773,
 0.6349639147847361 :+ (-1.2984575814159773)]

Prelude Data.Complex> map cosh a
[0.5403023058681398 :+ 0.0,
 0.5403023058681398 :+ (-0.0),
 0.8337300251311491 :+ 0.9888977057628651,
 0.8337300251311491 :+ (-0.9888977057628651)]

Prelude Data.Complex> map tanh a
[0.0 :+ 1.557407724654902,
 0.0 :+ (-1.557407724654902),
 1.0839233273386946 :+ 0.2717525853195117,
 1.0839233273386946 :+ (-0.2717525853195117)]

●複素数の平方根

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

z = x + iy, |z| = √(x2 + y2), θ = z の偏角 (-pi <= θ <= pi)
とすると
√(x + iy) =
√(|z| * e) = √|z| * eiθ/2  -- (1)
√(|z| * eiθ+ 2pi) = √|z| * eiθ/2 + pi  -- (2)

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

三角関数の半角の公式
sin2(θ/2) = (1 - cos θ) / 2
cos2(θ/2) = (1 + cos θ) / 2

y >= 0 の場合
√|z| * eiθ/2 = √|z| * (cos(θ/2) + i sin(θ/2))
            = √|z| * (√((1 + cos θ)/2) + i √((1 - cos θ)/2))
            = √((|z| + |z|cos θ)/2 + i √((|z| - |z|cos θ)/2) 
|z|cos θ = x だから
            = √((|z| + x) / 2) + i √((|z| - x) / 2)
y < 0 の場合、虚部の符号が - になる
            = √((|z| + x) / 2) - i √((|z| - x) / 2)

Haskell の sqrt は複素数にも対応しています。簡単な実行例を示します。

Prelude Data.Complex> sqrt (1.0 :+ 0.0)
1.0 :+ 0.0
Prelude Data.Complex> sqrt (2.0 :+ 0.0)
1.4142135623730951 :+ 0.0
Prelude Data.Complex> sqrt (3.0 :+ 0.0)
1.7320508075688772 :+ 0.0
Prelude Data.Complex> sqrt ((-1.0) :+ 0.0)
0.0 :+ 1.0
Prelude Data.Complex> sqrt ((-2.0) :+ 0.0)
0.0 :+ 1.4142135623730951
Prelude Data.Complex> sqrt ((-3.0) :+ 0.0)
0.0 :+ 1.7320508075688772

Prelude Data.Complex> a
[0.0 :+ 1.0,0.0 :+ (-1.0),1.0 :+ 1.0,1.0 :+ (-1.0)]
Prelude Data.Complex> map sqrt a
[0.7071067811865476 :+ 0.7071067811865475,
 0.7071067811865476 :+ (-0.7071067811865475),
 1.09868411346781 :+ 0.45508986056222733,
 1.09868411346781 :+ (-0.45508986056222733)]

Prelude Data.Complex> map (**2) (map sqrt a)
[6.123233995736766e-17 :+ 1.0,
 6.123233995736766e-17 :+ (-1.0),
 1.0 :+ 0.9999999999999998,
 1.0 :+ (-0.9999999999999998)]

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

Prelude Data.Complex> sqrt ((-2.0) :+ 0.0)
0.0 :+ 1.4142135623730951
Prelude Data.Complex> sqrt ((-2.0) :+ (-0.0))
0.0 :+ 1.4142135623730951
Prelude Data.Complex> sqrt ((-1e300) :+ 0.0)
0.0 :+ 1.0e150
Prelude Data.Complex> sqrt ((-1e300) :+ (-0.0))
0.0 :+ 1.0e150

M.Hiroi が使っているバージョン (GHC version 8.8.4) では、sqrt の分枝切断に不具合がある、もしくは負のゼロには対応していないのかもしれません。新しいバージョンでは修正されているかもしれません。

●逆三角関数

三角関数の逆関数を「逆三角関数 (inverse trigonometric function)」といいます。以下に Haskell に用意されている逆三角関数を示します。

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

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

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

Prelude Data.Complex> map asin [-1.0, -0.5, 0.0, 0.5, 1.0]
[-1.5707963267948966,
 -0.5235987755982989,
 0.0,
 0.5235987755982989,
 1.5707963267948966]

Prelude Data.Complex> map acos [-1.0, -0.5, 0.0, 0.5, 1.0]
[3.141592653589793,
 2.0943951023931957,
 1.5707963267948966,
 1.0471975511965979,
 0.0]

Prelude Data.Complex> map atan [-1e100, -1.0, 0.0, 1.0, 1e100]
[-1.5707963267948966,
 -0.7853981633974483,
 0.0,
 0.7853981633974483,
 1.5707963267948966]

Haskell には 2 引数の関数 atan2 y x も用意されています。これは他のプログラミング言語、たとえばC言語の数学関数 atan2 と同じです。

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

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

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

Prelude Data.Complex> map (\(x, y) -> atan2 y x) [(-1.0, 0.0),(-1.0, 1.0),(0.0, 1.0),(1.0, 1.0),
(1.0, 0.0),(1.0, -1.0),(0.0, -1.0),(-1.0, -1.0),(-1.0, -0.0)]
[3.141592653589793,
 2.356194490192345,
 1.5707963267948966,
 0.7853981633974483,
 0.0,
 -0.7853981633974483,
 -1.5707963267948966,
 -2.356194490192345,
 -3.141592653589793]

●複素数の逆三角関数

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

asin z = w (z, w は複素数)
z = sin w = (eiw - e-iw) / 2i
両辺に 2ieiw を掛けると
2iz(eiw) = (eiw)2 - 1
(eiw)2 - 2iz(eiw) - 1 = 0
eiw の二次方程式と考えて解くと
eiw = iz ± √(1 - z2)
両辺の対数をとって -i を掛け算し、平方根の主値 (+) を選ぶ
w = asin z = -i log(iz + √(1 - z2))

acos z と atan z は定義だけを示します。

acos z = pi/2 - asin z
atan z = i(log(1 - iz) - log(1 + iz)) / 2

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

Haskell の逆三角関数 (asin, acos, atan) は複素数に対応しています。簡単な実行例を示します。

Prelude Data.Complex> a
[0.0 :+ 1.0,0.0 :+ (-1.0),1.0 :+ 1.0,1.0 :+ (-1.0)]

Prelude Data.Complex> map asin a
[0.0 :+ 0.8813735870195428,
 0.0 :+ (-0.8813735870195429),
 0.6662394324925153 :+ 1.0612750619050355,
 0.6662394324925153 :+ (-1.0612750619050357)]
Prelude Data.Complex> map (sin . asin) a
[0.0 :+ 0.9999999999999997,
 0.0 :+ (-1.0),
 1.0 :+ 0.9999999999999998,
 1.0000000000000002 :+ (-1.0)]

Prelude Data.Complex> asin(2.0 :+ 0.0)
1.5707963267948966 :+ (-1.3169578969248166)
Prelude Data.Complex> asin(2.0 :+ (-0.0))
1.5707963267948966 :+ (-1.3169578969248166)
Prelude Data.Complex> asin((-4.0) :+ 0.0)
(-1.5707963267948966) :+ 2.0634370688955617
Prelude Data.Complex> asin((-4.0) :+ (-0.0))
(-1.5707963267948966) :+ 2.0634370688955617

Prelude Data.Complex> map acos a
[1.5707963267948966 :+ (-0.8813735870195429),
 1.5707963267948966 :+ 0.8813735870195428,
 0.9045568943023814 :+ (-1.0612750619050357),
 0.9045568943023814 :+ 1.0612750619050355]
Prelude Data.Complex> map (cos . acos) a
[8.659560562354932e-17 :+ 1.0,
 8.659560562354932e-17 :+ (-0.9999999999999997),
 0.9999999999999999 :+ 1.0,
 0.9999999999999998 :+ (-0.9999999999999998)]

Prelude Data.Complex> acos(2.0 :+ 0.0)
0.0 :+ 1.3169578969248164
Prelude Data.Complex> acos(2.0 :+ (-0.0))
0.0 :+ 1.3169578969248164
Prelude Data.Complex> acos((-4.0) :+ 0.0)
3.141592653589793 :+ (-2.0634370688955608)
Prelude Data.Complex> acos((-4.0) :+ (-0.0))
3.141592653589793 :+ (-2.0634370688955608)

Prelude Data.Complex> map atan a
[NaN :+ NaN,
 NaN :+ NaN,
 1.0172219678978514 :+ 0.4023594781085251,
 1.0172219678978514 :+ (-0.4023594781085249)]
Prelude Data.Complex> map (tan . atan) a
[NaN :+ NaN,
 NaN :+ NaN,
 1.0 :+ 1.0000000000000002,
 1.0000000000000004 :+ (-0.9999999999999999)]

Prelude Data.Complex> atan (0.0 :+ 2.0)
1.5707963267948966 :+ 0.5493061443340547
Prelude Data.Complex> atan ((-0.0) :+ 2.0)
1.5707963267948966 :+ 0.5493061443340547
Prelude Data.Complex> atan (0.0 :+ (-4.0))
(-1.5707963267948966) :+ (-0.25541281188299514)
Prelude Data.Complex> atan ((-0.0) :+ (-4.0))
(-1.5707963267948966) :+ (-0.25541281188299514)

M.Hiroi が使っているバージョン (GHC version 8.8.4) では、逆三角関数の分枝切断に不具合がある、もしくは負のゼロには対応していないのかもしれません。新しいバージョンでは修正されているかもしれません。

●逆双曲線関数

双曲線関数の逆関数を「逆双曲線関数 (inverse hyperbolic function)」といいます。Haskell に用意されている逆双曲線関数には asinh, acosh, atanh があります。双曲線関数と逆双曲線関数の定義域と値域を示します。

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

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

asinh x = y
y = sinh x
  = (ex - e-x) / 2
両辺に 2ex を掛けると
2yex = e2x - 1
e2x - 2yex - 1 = 0, (ex の 2 次方程式として解く)
ex = (2y ± √(4y2 + 4))/2 = y ± √(y2 + 1)
ex > 0 だから平方根の符号に + を選んで対数をとる
x = log (y + √(y2 + 1))
acosh x = y
y = cosh x
  = (ex + e-x) / 2
両辺に 2ex を掛けると
2yex = e2x + 1
e2x - 2yex + 1 = 0, (ex の 2 次方程式として解く)
ex = (2y ± √(4y2 - 4))/2 = y ± √(y2 - 1)
x = log (y ± √(y2 - 1))
平方根の符号 + を主値としてとると
x = log (y + √(y2 - 1)), (y >= 1)
atanh x = y
y = tanh x 
  = sinh x / cosh x
  = (ex - e-x) / (ex + e-x)
  = (e2x - 1) / (e2x + 1)
y(e2x + 1) = (e2x - 1)
(1 - y)e2x = 1 + y
e2x = (1 + y) / (1 - y)
x = (1 / 2) * log((1 + y) / (1 - y))

また、次の関係式を使って atanh から asinh と acosh を求めることができます。

asinh x = atanh(x / √(x2 + 1))
acosh x = atanh(√(1 - x2) / x)

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

Prelude Data.Complex> map asinh [1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5]
[1.1947632172871094,
 0.881373587019543,
 0.48121182505960347,
 0.0,
 -0.48121182505960347,
 -0.881373587019543,
 -1.1947632172871094]
Prelude Data.Complex> map (sinh . asinh) [1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5]
[1.5000000000000004,
 1.0,
 0.5,
 0.0,
 -0.5,
 -1.0,
 -1.5000000000000004]


julia> for x = [1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5] b = asinh(x); println(b); println(sinh(b)) end
1.1947632172871094
1.5000000000000002
0.881373587019543
1.0
0.48121182505960347
0.5
0.0
0.0
-0.48121182505960347
-0.5
-0.881373587019543
-1.0
-1.1947632172871094
-1.5000000000000002

Prelude Data.Complex> map acosh [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]
[0.0,
 0.9624236501192069,
 1.3169578969248166,
 1.566799236972411,
 1.762747174039086,
 1.9248473002384139,
 2.0634370688955608]
Prelude Data.Complex> map (cosh . acosh) [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]
[1.0,
 1.5,
 1.9999999999999998,
 2.5,
 3.0000000000000004,
 3.5000000000000004,
 4.000000000000001]

Prelude Data.Complex> map atanh [-0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75]
[-0.9729550745276566,
 -0.5493061443340548,
 -0.25541281188299536,
 0.0,
 0.25541281188299536,
 0.5493061443340548,
 0.9729550745276566]
Prelude Data.Complex> map (tanh . atanh) [-0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75]
[-0.75,
 -0.49999999999999994,
 -0.25,
 0.0,
 0.25,
 0.49999999999999994,
 0.75]

●複素数の逆双曲線関数

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

asinh z = log (z + √(z2 + 1))
acosh z = log (z + √(z2 - 1))
atanh z = (1 / 2) * log((1 + z) / (1 - z))
        = (1 / 2) * (log(1 + z) - log(1 - z))

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

Haskell の逆双曲線関数 (asinh, acosh, atanh) は複素数に対応しています。簡単な実行例を示します。

Prelude Data.Complex> a
[0.0 :+ 1.0,0.0 :+ (-1.0),1.0 :+ 1.0,1.0 :+ (-1.0)]

Prelude Data.Complex> map asinh a
[0.0 :+ 1.5707963267948966,
 0.0 :+ (-1.5707963267948966),
 1.0612750619050357 :+ 0.6662394324925153,
 1.0612750619050357 :+ (-0.6662394324925153)]
Prelude Data.Complex> map (sinh . asinh) a
[0.0 :+ 1.0,
 0.0 :+ (-1.0),
 1.0 :+ 1.0000000000000002,
 1.0 :+ (-1.0000000000000002)]

Prelude Data.Complex> asinh(0.0 :+ 2.0)
1.3169578969248166 :+ 1.5707963267948966
Prelude Data.Complex> asinh((-0.0) :+ 2.0)
1.3169578969248166 :+ 1.5707963267948966
Prelude Data.Complex> asinh(0.0 :+ (-4.0))
(-2.0634370688955617) :+ (-1.5707963267948966)
Prelude Data.Complex> asinh((-0.0) :+ (-4.0))
(-2.0634370688955617) :+ (-1.5707963267948966)

Prelude Data.Complex> map acosh a
[0.8813735870195429 :+ 1.5707963267948966,
 0.8813735870195429 :+ (-1.5707963267948966),
 1.0612750619050357 :+ 0.9045568943023814,
 1.0612750619050357 :+ (-0.9045568943023814)]
Prelude Data.Complex> map (cosh . acosh) a
[8.659560562354932e-17 :+ 1.0,
 8.659560562354932e-17 :+ (-1.0),
 0.9999999999999999 :+ 1.0,
 0.9999999999999999 :+ (-1.0)]

Prelude Data.Complex> acosh(0.0 :+ 0.0)
0.0 :+ 1.5707963267948966
Prelude Data.Complex> acosh(0.0 :+ (-0.0))
0.0 :+ 1.5707963267948966
Prelude Data.Complex> acosh((-4.0) :+ 0.0)
2.0634370688955608 :+ 3.141592653589793
Prelude Data.Complex> acosh((-4.0) :+ (-0.0))
2.0634370688955608 :+ 3.141592653589793

Prelude Data.Complex> map atanh a
[0.0 :+ 0.7853981633974483,
 0.0 :+ (-0.7853981633974483),
 0.4023594781085251 :+ 1.0172219678978514,
 0.4023594781085251 :+ (-1.0172219678978514)]
Prelude Data.Complex> map (tanh . atanh) a
[0.0 :+ 0.9999999999999998,
 0.0 :+ (-0.9999999999999998),
 1.0000000000000002 :+ 1.0,
 1.0000000000000002 :+ (-1.0)]

Prelude Data.Complex> atanh(2.0 :+ 0.0)
0.5493061443340549 :+ (-1.5707963267948966)
Prelude Data.Complex> atanh(2.0 :+ (-0.0))
0.5493061443340549 :+ (-1.5707963267948966)
Prelude Data.Complex> atanh((-4.0) :+ 0.0)
(-0.25541281188299536) :+ 1.5707963267948966
Prelude Data.Complex> atanh((-4.0) :+ (-0.0))
(-0.25541281188299536) :+ 1.5707963267948966

M.Hiroi が使っているバージョン (GHC version 8.8.4) では、逆双曲線関数の分枝切断に不具合がある、もしくは負のゼロには対応していないのかもしれません。新しいバージョンでは修正されているかもしれません。

●参考文献, URL

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

Copyright (C) 2021 Makoto Hiroi
All rights reserved.

[ PrevPage | Haskell | NextPage ]