M.Hiroi's Home Page

Functional Programming

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

[ PrevPage | Scheme | NextPage ]

複素数

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

他の言語では、FORTRAN や Common Lisp が昔から複素数型をサポートしています。Common Lisp の場合、基本的な数学関数でも複素数を適用できるのであれば、引数に複素数を渡して計算することができます。もちろん、Scheme でも複素数を取り扱うことができます。今回は Scheme (R7RS-small) の複素数について説明します。

●Scheme の数

Scheme では数を整数 (integer)、有理数 (rational number)、実数 (real number)、複素数 (complex number) の 4 つに分類しています。これらの分類は階層的になっていて、すべての整数は有理数であり、すべての有理数は実数であり、すべての実数は複素数となっています。

Scheme の場合、整数はメモリの許す限り任意の精度で扱うことができます。これを「正確な数」といいます。有理数は分子と分母を整数で保持しているので、これも正確な数になります。通常、実数は浮動小数点数 (floating point number) として表現されます。浮動小数点数には IEEE 754 という標準仕様があり、近代的なプログラミング言語のほとんどは、IEEE 754 に準拠した浮動小数点数をサポートしています。浮動小数点数はすべての小数を正確に表現することはできません。このため、実数は「不正確な数」になります。

IEEE 754 には通常の数値以外にも、負のゼロ (-0.0)、正負の無限大 (∞, -∞)、NaN (Not a Number, 非数) といった値が定義されています。これらの値は Scheme (R7RS-small) でも取り扱うことができます。正負の無限大は +inf.0, -inf.0 と、NaN は +nan.0 と表記されます。なお、仕様 (R7RS-small) ではゼロと負のゼロの区別を要求していないので、動作は処理系に依存します。区別する処理系の場合、(eqv? 0.0 -0.0) は偽を返します。

●無限大

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

Gauche での実行例を示します。

gosh[r7rs.user]> 1e308
1.0e308
gosh[r7rs.user]> 1e309
+inf.0
gosh[r7rs.user]> (* 1e308 2)
+inf.0
gosh[r7rs.user]> -1e308
-1.0e308
gosh[r7rs.user]> -1e309
-inf.0
gosh[r7rs.user]> (* -1e308 2)
-inf.0
gosh[r7rs.user]> (/ 1.0 0.0)
+inf.0
gosh[r7rs.user]> (/ -1.0 0.0)
-inf.0
gosh[r7rs.user]> (log 0.0)
-inf.0

Scheme の場合、無限大の判定はライブラリ inexact の関数 finite?, infinite? を使います。finite? は +inf.0, -inf.0, +nan.0 以外のすべての数に対して真を返します。逆に、infinite? は +inf.0, -inf.0, +nan.0 に対して真を返します。簡単な例を示しましょう。

gosh[r7rs.user]> +inf.0
+inf.0
gosh[r7rs.user]> (finite? +inf.0)
#f
gosh[r7rs.user]> (infinite? +inf.0)
#t
gosh[r7rs.user]> (finite? 1)
#t
gosh[r7rs.user]> (infinite? 1)
#f
gosh[r7rs.user]> (finite? 1.2345)
#t
gosh[r7rs.user]> (infinite? 1.2345)
#f

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

gosh[r7rs.user]> (< -inf.0 +inf.0)
#t
gosh[r7rs.user]> (> -inf.0 +inf.0)
#f
gosh[r7rs.user]> (= -inf.0 +inf.0)
#f
gosh[r7rs.user]> (= +inf.0 +inf.0)
#t
gosh[r7rs.user]> (< -inf.0 0)
#t
gosh[r7rs.user]> (< 0.0 +inf.0)
#t
gosh[r7rs.user]> (+ +inf.0 10)
+inf.0
gosh[r7rs.user]> (- +inf.0 10)
+inf.0
gosh[r7rs.user]> (* +inf.0 10)
+inf.0
gosh[r7rs.user]> (/ +inf.0 10)
+inf.0
gosh[r7rs.user]> (+ +inf.0 +inf.0)
+inf.0
gosh[r7rs.user]> (* +inf.0 +inf.0)
+inf.0
gosh[r7rs.user]> (- +inf.0 +inf.0)
+nan.0
gosh[r7rs.user]> (/ +inf.0 +inf.0)
+nan.0
gosh[r7rs.user]> (+ +inf.0 -inf.0)
+nan.0
gosh[r7rs.user]> (* +inf.0 0.0)
+nan.0

●負のゼロ

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

gosh[r7rs.user]> -1e-323
-1.0e-323
gosh[r7rs.user]> -1e-324
-0.0
gosh[r7rs.user]> (/ -1e-323 2.0)
-5.0e-324
gosh[r7rs.user]> (/ -1e-323 4.0)
-0.0
gosh[r7rs.user]> (/ 1 -inf.0)
-0.0
gosh[r7rs.user]> (/ -1 +inf.0)
-0.0
gosh[r7rs.user]> (* -1.0 0.0)
-0.0

標準では、演算子 (C言語の == など) による 0.0 と -0.0 の比較は等しいと判定されます。Scheme の場合、ゼロと負のゼロを区別する処理系では関数 eqv? で 0.0 と -0.0 を区別することができます。また、任意の正の数を -0.0 で除算すると -inf.0 になるので、それを使って判別することもできます。

gosh[r7rs.user]> (= 0.0 -0.0)
#t
gosh[r7rs.user]> (eqv? 0.0 -0.0)
#t
gosh[r7rs.user]> (/ 1.0 -0.0)
-inf.0
gosh[r7rs.user]> (/ 1.0 0.0)
+inf.0

Gauche の場合、(eqv? 0.0 -0.0) が #t を返すので、ゼロと負のゼロの区別を厳密にはしていないのかもしれません。なお、-0.0 は数学関数 (C言語の数学関数 atan2 など, Scheme では 2 引数の関数 atan) や複素数の演算処理などで使われます。

gosh[r7rs.user]> (sqrt 0.0)
0.0
gosh[r7rs.user]> (sqrt -0.0)
-0.0
gosh[r7rs.user]> (atan 0.0 -1.0)
3.141592653589793
gosh[r7rs.user]> (atan -0.0 -1.0)
-3.141592653589793

●非数

NaN は数ではないことを表す特別な値 (非数) です。一般的には 0.0 / 0.0 といった不正な演算を行うと、その結果は NaN になります。

gosh[r7rs.user]> +nan.0
+nan.0
gosh[r7rs.user]> (/ 0.0 0.0)
+nan.0
gosh[r7rs.user]> (/ +nan.0 +nan.0)
+nan.0
gosh[r7rs.user]> (/ +inf.0 +inf.0)
+nan.0
gosh[r7rs.user]> (nan? +nan.0)
#t
gosh[r7rs.user]> (nan? 0.0)
#f

Scheme の場合、NaN はライブラリ inexact の関数 nan? で判別することができます。

●Scheme の複素数

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

Scheme は複素数 \(z = x + iy\) を x+yi と表記します。符号 (+, -) の前後に空白を入れてはいけません。Gauche の場合、x, y が整数や有理数のときは実数に変換されます。実部 x を省略すると x は 0.0 になります。虚部 y を省略して +i, -i とすると、0.0+1.0i, 0.0-1.0i になります。

このほかに、ライブラリ complex には複素数を生成する関数 make-rectangular、実部を求める関数 real-part、虚部を求める関数 imag-part が用意されています。

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

gosh[r7rs.user]> +i
0.0+1.0i
gosh[r7rs.user]> -i
0.0-1.0i
gosh[r7rs.user]> 1+i
1.0+1.0i
gosh[r7rs.user]> 1-i
1.0-1.0i
gosh[r7rs.user]> -1-2i
-1.0-2.0i
gosh[r7rs.user]> (make-rectangular 1 2)
1.0+2.0i
gosh[r7rs.user]> (make-rectangular (+ 1 2) (* 3 4))
3.0+12.0i
gosh[r7rs.user]> (define a 1.2+3.4i)
a
gosh[r7rs.user]> a
1.2+3.4i
gosh[r7rs.user]> (real-part a)
1.2
gosh[r7rs.user]> (imag-part a)
3.4

Gauche は複素数 z の虚部が 0 の場合、z を実数に変換します。

gosh[r7rs.user]> 1.0+0.0i
1.0
gosh[r7rs.user]> 1.0-0.0i
1.0
gosh[r7rs.user]> (- 1.0+1.0i +i)
1.0
gosh[r7rs.user]> (+ 1.0+1.0i -i)
1.0
gosh[r7rs.user]> (real? 1.0-0.0i)
#t
gosh[r7rs.user]> (real? 1.0+0.0i)
#t

Gauche では複素数 \(x+0.0i\) と \(x-0.0i\) の区別がつかないことに注意してください。

複素数は極形式 \(z = r(\cos \theta + i \sin \theta)\) で表すことができます。このとき、r を絶対値、\(\theta\) を偏角といいます。Scheme では複素数を極形式 r@\(\theta\) で記述することができます。ライブラリ complex には絶対値を求める関数 magnitude、偏角を求める関数 angle、絶対値と偏角から複素数を生成する関数 make-polar が用意されています。angle の返り値 \(\theta\) は \(-\pi \lt \theta \leq \pi\) (\(\pi\) : 円周率) です。

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

gosh[r7rs.user]> (define b 1+i)
b
gosh[r7rs.user]> b
1.0+1.0i
gosh[r7rs.user]> (magnitude b)
1.4142135623730951
gosh[r7rs.user]> (angle b)
0.7853981633974483
gosh[r7rs.user]> (make-polar (magnitude b) (angle b))
1.0000000000000002+1.0i
gosh[r7rs.user]> 1.4142@0.7854
0.9999885733673903+0.9999922465372674i

gosh[r7rs.user]> 1@0
1.0
gosh[r7rs.user]> 1@1
0.5403023058681398+0.8414709848078965i
gosh[r7rs.user]> 1@2
-0.4161468365471424+0.9092974268256817i
gosh[r7rs.user]> 1@3
-0.9899924966004454+0.1411200080598672i
gosh[r7rs.user]> 1@-1
0.5403023058681398-0.8414709848078965i
gosh[r7rs.user]> 1@-2
-0.4161468365471424-0.9092974268256817i
gosh[r7rs.user]> 1@-3
-0.9899924966004454-0.1411200080598672i

Gauche の場合、\(-1+0.0i\) と \(-1-0.0i\) の偏角 \(\theta\) は \(\pi\) になります。

gosh[r7rs.user]> (angle -1+0.0i)
3.141592653589793
gosh[r7rs.user]> (angle -1-0.0i)
3.141592653589793
gosh[r7rs.user]> (angle -1-0.01i)
-3.131592986903128
gosh[r7rs.user]> (angle -1-0.001i)
-3.1405926539231266
gosh[r7rs.user]> (angle -1-0.0001i)
-3.1414926535901264

+0.0i と -0.0i を区別できる処理系であれば、\(-1-0.0i\) の偏角は \(-\pi\) になります。

実部と虚部の値は +inf.0, -inf.0, +nan.0 になることもあります。

gosh[r7rs.user]> +inf.0+inf.0i
+inf.0+inf.0i
gosh[r7rs.user]> +inf.0-inf.0i
+inf.0-inf.0i
gosh[r7rs.user]> +nan.0+nan.0i
+nan.0+nan.0i
gosh[r7rs.user]> +nan.0+inf.0i
+nan.0+inf.0i
gosh[r7rs.user]> +inf.0+nan.0i
+inf.0+nan.0i
gosh[r7rs.user]> (* 1e300+1e300i 1e300+1e300i)
+nan.0+inf.0i

実部と虚部の値が +inf.0, -inf.0, +nan.0 の場合、述語 infinite? は真を返します。

gosh[r7rs.user]> (infinite? +inf.0+i)
#t
gosh[r7rs.user]> (infinite? 1+inf.0i)
#t
gosh[r7rs.user]> (infinite? +nan.0+inf.0i)
#t

●数の型述語

Scheme の数は以下の述語で判定することができます。

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

gosh[r7rs.user]> (number? 1+i)
#t
gosh[r7rs.user]> (complex? 1+i)
#t
gosh[r7rs.user]> (real? 1+i)
#f
gosh[r7rs.user]> (integer? 1+i)
#f
gosh[r7rs.user]> (integer? 1)
#t
gosh[r7rs.user]> (real? 1)
#t
gosh[r7rs.user]> (complex? 1)
#t
gosh[r7rs.user]> (exact? 1)
#t
gosh[r7rs.user]> (exact? 1/2)
#t
gosh[r7rs.user]> (exact? 1.5)
#f
gosh[r7rs.user]> (exact? 1+i)
#f
gosh[r7rs.user]> (inexact? 1)
#f
gosh[r7rs.user]> (inexact? 1/2)
#f
gosh[r7rs.user]> (inexact? 1.5)
#t
gosh[r7rs.user]> (inexact? 1+i)
#t

●複素数の四則演算

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

\( \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} \)

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

gosh[r7rs.user]> (define a 1+2i)
a
gosh[r7rs.user]> a
1.0+2.0i
gosh[r7rs.user]> (define b 3+4i)
b
gosh[r7rs.user]> b
3.0+4.0i
gosh[r7rs.user]> (+ a b)
4.0+6.0i
gosh[r7rs.user]> (- a b)
-2.0-2.0i
gosh[r7rs.user]> (* a b)
-5.0+10.0i
gosh[r7rs.user]> (/ a b)
0.44+0.08i
gosh[r7rs.user]> (= a a)
#t
gosh[r7rs.user]> (= a b)
#f
gosh[r7rs.user]> (eqv? a a)
#t
gosh[r7rs.user]> (eqv? a b)
#f

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

複素数を引数にとる指数関数はオイラー (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 \lt \theta \leq \pi) \end{eqnarray} \end{array} \)

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

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

ライブラリ inexact の関数 exp, log, expt は複素数に対応しています。簡単な例を示しましょう。

gosh[r7rs.user]> (define pi (angle -1.0+0.0i))
pi
gosh[r7rs.user]> pi
3.141592653589793
gosh[r7rs.user]> (exp 0.0)
1.0
gosh[r7rs.user]> (exp (make-rectangular 0.0 (/ pi 4)))
0.7071067811865476+0.7071067811865475i
gosh[r7rs.user]> (exp (make-rectangular 0.0 (/ pi 2)))
6.123233995736766e-17+1.0i
gosh[r7rs.user]> (exp (make-rectangular 0.0 pi))
-1.0+1.2246467991473532e-16i
gosh[r7rs.user]> (exp (make-rectangular 1.0 1.0))
1.4686939399158851+2.2873552871788423i

gosh[r7rs.user]> (log 1.0+1.0i)
0.3465735902799727+0.7853981633974483i
gosh[r7rs.user]> (log 1.0+0.0i)
0.0
gosh[r7rs.user]> (log 0.0+1.0i)
0.0+1.5707963267948966i
gosh[r7rs.user]> (log 1.0-1.0i)
0.3465735902799727-0.7853981633974483i
gosh[r7rs.user]> (log 1e100+1e100i)
230.60508288968455+0.7853981633974483i

gosh[r7rs.user]> (expt 1.0+1.0i 0.0)
1.0
gosh[r7rs.user]> (expt 1.0+1.0i 1.0)
1.0000000000000002+1.0i
gosh[r7rs.user]> (expt 1.0+1.0i 2.0)
1.2246467991473532e-16+2.0i
gosh[r7rs.user]> (expt 1.0+1.0i 3.0)
-2.0+2.0000000000000004i
gosh[r7rs.user]> (expt 1.0+2.0i 3.0+4.0i)
0.129009594074467+0.03392409290517014i
gosh[r7rs.user]> (expt 1.0+1.0i 1.0+1.0i)
0.2739572538301211+0.5837007587586147i

●複素数の三角関数

複素数の三角関数の定義は、オイラーの公式から導かれる式の \(\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}\)

Scheme のライブラリ inexact にある三角関数 (sin, cos, tan) は複素数にも対応しています。簡単な実行例を示します。

gosh[r7rs.user]> (sin +i)
0.0+1.1752011936438014i
gosh[r7rs.user]> (magnitude (sin +i))
1.1752011936438014
gosh[r7rs.user]> (sin 1.0+i)
1.2984575814159773+0.6349639147847361i
gosh[r7rs.user]> (magnitude (sin 1.0+i))
1.4453965766582495
gosh[r7rs.user]> (sin 1.0-i)
1.2984575814159773-0.6349639147847361i
gosh[r7rs.user]> (magnitude (sin 1.0-i))
1.4453965766582495

gosh[r7rs.user]> (cos +i)
1.5430806348152437
gosh[r7rs.user]> (cos -i)
1.5430806348152437
gosh[r7rs.user]> (cos 1.0+i)
0.8337300251311491-0.9888977057628651i
gosh[r7rs.user]> (magnitude (cos 1.0+i))
1.2934544550420957
gosh[r7rs.user]> (cos 1.0-i)
0.8337300251311491+0.9888977057628651i
gosh[r7rs.user]> (magnitude (cos 1.0-i))
1.2934544550420957

gosh[r7rs.user]> (tan +i)
-0.0+0.7615941559557649i
gosh[r7rs.user]> (magnitude (tan +i))
0.7615941559557649
gosh[r7rs.user]> (tan -i)
0.0-0.7615941559557649i
gosh[r7rs.user]> (magnitude (tan -i))
0.7615941559557649
gosh[r7rs.user]> (magnitude (tan 1.0+i))
1.1174700207060706
gosh[r7rs.user]> (tan 1.0+i)
0.27175258531951174+1.0839233273386948i
gosh[r7rs.user]> (tan 1.0-i)
0.27175258531951174-1.0839233273386948i
gosh[r7rs.user]> (magnitude (tan 1.0-i))
1.1174700207060706
-- 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}\)

R7RS-small の場合、双曲線関数は定義されていませんが、Gauche では双曲線関数 (sinh, cosh, tanh) を使用することができます。簡単な使用例を示します。

gosh> (sinh +i)
0.0+0.8414709848078965i
gosh> (sinh -i)
0.0-0.8414709848078965i
gosh> (sinh 1.0+i)
0.6349639147847361+1.2984575814159773i
gosh> (sinh 1.0)
1.1752011936438014
gosh> (sinh 0.0)
0.0

gosh> (cosh +i)
0.5403023058681398
gosh> (cosh -i)
0.5403023058681398
gosh> (cosh 1.0+i)
0.8337300251311491+0.9888977057628651i
gosh> (cosh 1.0)
1.5430806348152437
gosh> (cosh 0.0)
1.0

gosh> (tanh +i)
0.0+1.557407724654902i
gosh> (tanh -i)
0.0-1.557407724654902i
gosh> (tanh 1.0+i)
1.0839233273386948+0.27175258531951174i
gosh> (tanh 1.0)
0.7615941559557649
gosh> (tanh 0.0)
0.0

●複素数の平方根

複素数 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}} \)

Scheme のライブラリ inexact にある sqrt は複素数にも対応しています。簡単な実行例を示します。

gosh[r7rs.user]> (sqrt 1)
1
gosh[r7rs.user]> (sqrt 2)
1.4142135623730951
gosh[r7rs.user]> (sqrt 3)
1.7320508075688772
gosh[r7rs.user]> (sqrt -1)
0.0+1.0i
gosh[r7rs.user]> (sqrt -2)
0.0+1.4142135623730951i
gosh[r7rs.user]> (sqrt -3)
0.0+1.7320508075688772i

gosh[r7rs.user]> (define a (sqrt 1.0+i))
a
gosh[r7rs.user]> a
1.0986841134678098+0.45508986056222733i
gosh[r7rs.user]> (* a a)
0.9999999999999998+0.9999999999999999i

gosh[r7rs.user]> (define a (sqrt 1.0-i))
a
gosh[r7rs.user]> a
1.0986841134678098-0.45508986056222733i
gosh[r7rs.user]> (* a a)
0.9999999999999998-0.9999999999999999i

gosh[r7rs.user]> (define a (sqrt -1.0+i))
a
gosh[r7rs.user]> a
0.4550898605622274+1.0986841134678098i
gosh[r7rs.user]> (* a a)
-0.9999999999999997+1.0i

gosh[r7rs.user]> (define a (sqrt -1.0-i))
a
gosh[r7rs.user]> a
0.4550898605622274-1.0986841134678098i
gosh[r7rs.user]> (* a a)
-0.9999999999999997-1.0i

●逆三角関数

三角関数の逆関数を「逆三角関数 (inverse trigonometric function)」といいます。以下に Scheme (R7RS-small) の逆三角関数を示します。

ここでは引数 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 と表示します。

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

gosh[r7rs.user]> (for-each (lambda (x) (display (asin x)) (newline)) '(-1.0 -0.5 0.0 0.5 1.0))
-1.5707963267948966
-0.5235987755982989
0.0
0.5235987755982989
1.5707963267948966
#<undef>
gosh[r7rs.user]> (for-each (lambda (x) (display (acos x)) (newline)) '(-1.0 -0.5 0.0 0.5 1.0))
3.141592653589793
2.0943951023931957
1.5707963267948966
1.0471975511965979
0.0
#<undef>
gosh[r7rs.user]> (for-each (lambda (x) (display (atan x)) (newline)) '(-1e300 -1.0 0.0 1.0 1e300))
-1.5707963267948966
-0.7853981633974483
0.0
0.7853981633974483
1.5707963267948966
#<undef>

Scheme (R7RS-small) には 2 引数の関数 atan も用意されています。これは他のプログラミング言語、たとえばC言語の数学関数 atan2 と同じです。

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

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

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

gosh[r7rs.user]> (atan 0.0 1.0)
0.0
gosh[r7rs.user]> (atan 1.0 1.0)
0.7853981633974483
gosh[r7rs.user]> (atan 1.0 0.0)
1.5707963267948966
gosh[r7rs.user]> (atan 1.0 -1.0)
2.356194490192345
gosh[r7rs.user]> (atan 0.0 -1.0)
3.141592653589793
gosh[r7rs.user]> (atan -1.0 1.0)
-0.7853981633974483
gosh[r7rs.user]> (atan -1.0 0.0)
-1.5707963267948966
gosh[r7rs.user]> (atan -1.0 -1.0)
-2.356194490192345
gosh[r7rs.user]> (atan -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}\)

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

gosh[r7rs.user]> (asin 2.0)
1.5707963267948966-1.3169578969248166i
gosh[r7rs.user]> (sin (asin 2.0))
1.9999999999999998-1.0605752387249067e-16i
gosh[r7rs.user]> (asin +i)
-0.0+0.8813735870195428i
gosh[r7rs.user]> (sin (asin +i))
-0.0+0.9999999999999997i
gosh[r7rs.user]> (asin 1.0+i)
0.6662394324925153+1.0612750619050355i
gosh[r7rs.user]> (sin (asin 1.0+i))
1.0+0.9999999999999998i

gosh[r7rs.user]> (acos 2.0)
0.0+1.3169578969248166i
gosh[r7rs.user]> (cos (acos 2.0))
1.9999999999999998
gosh[r7rs.user]> (acos +i)
1.5707963267948966-0.8813735870195428i
gosh[r7rs.user]> (cos (acos +i))
8.659560562354932e-17+0.9999999999999997i
gosh[r7rs.user]> (acos 1.0+i)
0.9045568943023813-1.0612750619050355i
gosh[r7rs.user]> (cos (acos 1.0+i))
1.0+0.9999999999999998i

gosh[r7rs.user]> (atan 1.0+i)
1.0172219678978514+0.4023594781085251i
gosh[r7rs.user]> (tan (atan 1.0+i))
1.0+0.9999999999999999i

●参考文献, URL

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

Copyright (C) 2021 Makoto Hiroi
All rights reserved.

[ PrevPage | Scheme | NextPage ]