近年、多くのプログラミング言語で「複素数 (complex number)」がサポートされるようになりました。たとえば、C言語では 1999 年に発行された規格 C99 で複素数型が導入されました。Go 言語や Python でも複素数型をサポートしていますし、複素数用の標準ライブラリを用意している言語 (C++, Ruby, Haskell など) も多くあります。
他の言語では、FORTRAN や Common Lisp が昔から複素数型をサポートしています。Common Lisp の場合、基本的な数学関数でも複素数を適用できるのであれば、引数に複素数を渡して計算することができます。Haskell の場合、モジュール Data.Complex をインポートすると、複素数を使用することができます。今回は 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 で判別することができます。
数学では複素数 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 \theta + i \sin \theta)\) で表すことができます。このとき、r を絶対値、\(\theta\) を偏角といいます。絶対値は関数 magnitude で求めることができます。偏角は複素平面において正の実軸とベクトル (x, y) との角度を表します。偏角を求めるには関数 phase を使います。phase の返り値 \(\theta\) は \(-\pi \leq \theta \leq \pi\) (\(\pi\) : 円周率) です。
極形式 \(z = r (\cos \theta + i \sin \theta)\) で複素数を生成するときは関数 mkPolar を使います。
mkPolar :: Floating a => a -> a -> Complex a
第 1 引数が絶対値 r、第 2 引数が偏角 \(\theta\) を表します。複素数 z はオイラーの公式 \(e^{i\theta} = \cos \theta + i \sin \theta\) を使って \(z = r e^{i\theta}\) と書くこともできます。ここで、\(\theta\) に \(\pi\) を代入するとオイラーの等式 \(e^{i\pi} + 1 = 0\) になります。また、下記のようにド・モアヴルの公式も導出することができます。
関数 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 の偏角 \(\theta\) は \(\pi\) になり、-1.0-0.0i の偏角は \(-\pi\) になります。ゼロと負のゼロを区別しないプログラミング言語では、偏角 \(\theta\) の範囲を \(-\pi \lt \theta \leq \pi\) に制限して、-1.0 + 0.0i (== -1.0 - 0.0i) の偏角を \(\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) の公式から導くことができます。
複素数の対数関数は複素数 z を絶対値 \(|z|\) と偏角 \(\theta\) を使って導くことができます。
複素数 x, y のべき乗 \(x^y\) は次式で求めることができます。
関数 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 + iy)\) は負の実軸 (\(-\infty \lt x \lt 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 を区別しない処理系では、偏角 \(\theta\) の範囲を \(-\pi \lt \theta \leq \pi\) に制限することで、\(\log z\) の返り値を (\(-\pi\) を取り除いて) 一つにすることができます。
複素数の三角関数の定義は、オイラーの公式から導かれる式の \(\theta\) を複素数 \(z\) に変えたものになります。
\(\sin z, \cos z\) に純虚数 \(ix\) を与えると双曲線関数 (\(\sinh x, \cosh x\)) になります。
これに三角関数の加法定理 [*1] を使うと次の式が導かれます。
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]
複素数の双曲線関数の定義は、実数の定義で引数 x を複素数 z に変えたものになります。
sinh z, cosh z に純虚数 ix を与えると三角関数 (sin x, cos x) になります。
これに双曲線関数の加法定理を使うと、次の式が導かれます。
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 の平方根は次の式で求めることができます。
式 (1) を平方根の主値といいます。角度は \(2\pi\) を足すと同じ角度になるので、式 (2) がもう一つの解になります。三角関数の半角の公式を使うと、式 (1) から次の式が導かれます。
式 (1) を平方根の主値といいます。角度は \(2\pi\) を足すと同じ角度になるので、式 (2) がもう一つの解になります。三角関数の半角の公式を使うと、式 (1) から次の式が導かれます。
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 x\) は \(\log x\) と同じ分枝切断を持っています。x を負の整数とすると sqrt(x) の解は \(i \sqrt x\) になりますが、もうひとつ \(-i \sqrt 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 は無数に存在します。つまり、逆三角関数の返り値は無数にあることになりますが、通常は一つの値を返すように範囲を制限します。これを「主値」といいます。
逆三角関数の主値を以下に示します。
本ページでは、数式の表示に JavaScript のライブラリ MathJax を使っています。MathJax では、逆三角関数を arcsin, arccos, arctan と表示します。
簡単な実行例を示します。
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 は直交座標系においてベクトル (x, y) と x 軸との角度を求める関数です。複素平面で考えると、複素数 \(x + iy\) の偏角 \(\theta\) を求めることと同じです。返り値 (角度 \(\theta\)) の範囲は \(-\pi \leq \theta \leq \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]
複素数の逆三角関数の定義は、複素数の三角関数の定義から導くことができます。\(\arcsin z\) の定義は次のようになります。
\(\arccos z, \arctan z\) は定義だけを示します。
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 があります。MathJax では逆双曲線関数を \(\sinh^{-1} x, \cosh^{-1} x, \tanh^{-1} x \) で表します。双曲線関数と逆双曲線関数の定義域と値域を示します。
x と y は実数です。x = cosh y の逆関数 y = acosh x を満たす y の値は 2 つありますが、ここでは \(y \geq 0\) を主値として選ぶことにします。
逆双曲線関数の定義は双曲線関数の定義から導くことができます。
関数 \(\tanh^{-1} x\) を使うと、次の関係式から \(\sinh^{-1} x, \ \cosh^{-1} 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 に変えたものになります。
参考文献, URL 7 によると、\(\cosh^{-1} z\) を式 (1) でプログラムすると「分枝切断線」が複雑になるため、他の式 (たとえば (2) など) でプログラムする処理系が多いようです。ちなみに、ANSI Common Lisp では \(\cosh^{-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) では、逆双曲線関数の分枝切断に不具合がある、もしくは負のゼロには対応していないのかもしれません。新しいバージョンでは修正されているかもしれません。