近年、多くのプログラミング言語で「複素数 (complex number)」がサポートされるようになりました。たとえば、C言語では 1999 年に発行された規格 C99 で複素数型が導入されました。Go 言語や Python でも複素数型をサポートしていますし、複素数用の標準ライブラリを用意している言語 (C++, Ruby, Haskell など) も多くあります。他の言語では、FORTRAN や Common Lisp が昔から複素数型をサポートしています。今回はC++の複素数についてまとめてみました。
C言語と同様にC++は標準で「整数 (integer)」と「浮動小数点数 (floating point number)」を使うことができます。下表に基本的な数を示します。
型名 | 範囲 |
---|---|
char | -128 ~ 127 (1 バイト) |
unsigned char | 0 ~ 255 (1 バイト) |
short int | -32768 ~ 32767 (2 バイト) |
unsigned short int | 0 ~ 65535 (2 バイト) |
int | -2147483648 ~ 2147483647 (4 バイト) |
unsigned int | 0 ~ 4294967295 (4 バイト) |
long int | -9223372036854775808 ~ 9223372036854775807 (8 バイト) |
unsigned long int | 0 ~ 18446744073709551615 (8 バイト) |
long long int | -9223372036854775808 ~ 9223372036854775807 (8 バイト) |
unsigned long long int | 0 ~ 18446744073709551615 (8 バイト) |
float | 1.1754944E-38 ~ 3.4028235E+38 (32 bit 単精度) |
double | 2.22507E-308 ~ 1.79769E+308 (64 bit 倍精度) |
long double | 3.362103E-4932 ~ 1.189731e+4932 (拡張倍精度) |
char は文字 (character) を表すデータ型ですが、文字コードを整数として扱うことができるので、ここでは整数型に含めています。unsigned は無符号整数を表します。short int, long int, long long int の int は省略してもかまいません。C言語と同様に、C++では整数値に 0x を付けると 16 進数、0 から始まる整数値は 8 進数となります。
C/C++の場合、数値の範囲は処理系によってかわります。規格で定められているのは最低限の条件だけです。一般に、32 bit 処理系では int, long が 4 バイトで、64 bit 処理系では int が 4 バイト、long が 4 バイトか 8 バイトになるようです。
浮動小数点数には IEEE 754 という標準仕様があり、近代的なプログラミング言語のほとんどは、IEEE 754 に準拠した浮動小数点数をサポートしています。浮動小数点数はすべての小数を正確に表現することはできないので、その値は近似的 (不正確) なものとなります。
IEEE 754 には通常の数値以外にも、負のゼロ (-0.0)、正負の無限大 (∞, -∞)、NaN (Not a Number, 非数) といった値が定義されています。これらの値はC++でも取り扱うことができます。
一般に、無限大は値のオーバーフロー、ゼロ除算 (数値 / 0.0)、数学関数の計算結果 (たとえば log(0.0)) などで発生します。標準ライブラリ <limits> のクラス numeric_limits<T> には正の無限大を取得する関数 infinity が定義されています。
また、標準ライブラリ <cmath> には、無限大を判定する関数 isinf や有限な数を判定する関数 isfinite も用意されています。isinf(x) は引数 x が正負の無限大であれば真を返します。isfinite(x) は x が正負の無限大ではなく非数でもなければ真を返します。
簡単な使用例を示しましょう。
リスト : 無限大の使用例 (inf1.cpp) #include <iostream> #include <limits> #include <cmath> using namespace std; int main() { float a = numeric_limits<float>::infinity(); double b = numeric_limits<double>::infinity(); long double c = numeric_limits<long double>::infinity(); double d = numeric_limits<double>::max(); double e = -d; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; cout << "-a = " << -a << endl; cout << "-b = " << -b << endl; cout << "-c = " << -c << endl; cout << "d = " << d << endl; cout << "e = " << e << endl; cout << "isinf(b) = " << isinf(b) << endl; cout << "isinf(d) = " << isinf(d) << endl; cout << "isfinite(b) = " << isfinite(b) << endl; cout << "isfinite(d) = " << isfinite(d) << endl; cout << "d * 2 = " << d * 2.0 << endl; cout << "e * 2 = " << e * 2.0 << endl; cout << "1.0 / 0.0 = " << 1.0 / 0.0 << endl; cout << "log(0.0) = " << log(0.0) << endl; }
$ clang++ inf1.cpp $ ./a.out a = inf b = inf c = inf -a = -inf -b = -inf -c = -inf d = 1.79769e+308 e = -1.79769e+308 isinf(b) = 1 isinf(d) = 0 isfinite(b) = 0 isfinite(d) = 1 d * 2 = inf e * 2 = -inf 1.0 / 0.0 = inf log(0.0) = -inf
無限大は他の数値と比較したり演算することもできますが、結果が非数 (NaN, 出力関数では nan, -nan と表示) になることもあります。
リスト : 無限大の使用例 (inf2.cpp) #include <iostream> #include <limits> #include <cmath> using namespace std; int main() { double a = numeric_limits<double>::infinity(); double b = -a; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "a == a => " << (a == a) << endl; cout << "a == b => " << (a == b) << endl; cout << "a != a => " << (a != a) << endl; cout << "a != b => " << (a != b) << endl; cout << "a > b => " << (a > b) << endl; cout << "a < b => " << (a < b) << endl; cout << "a > 0.0 => " << (a > 0.0) << endl; cout << "a < 0.0 => " << (a < 0.0) << endl; cout << "b < 0.0 => " << (b < 0.0) << endl; cout << "a + 10.0 = " << a + 10.0 << endl; cout << "a - 10.0 = " << a - 10.0 << endl; cout << "a * 10.0 = " << a * 10.0 << endl; cout << "a / 10.0 = " << a / 10.0 << endl; cout << "a + a = " << a + a << endl; cout << "a * a = " << a * a << endl; cout << "a - a = " << a - a << endl; cout << "a * a = " << a / a << endl; cout << "a + b = " << a + b << endl; cout << "a * 0.0 = " << a * 0.0 << endl; }
$ clang++ inf2.cpp $ ./a.out a = inf b = -inf a == a => 1 a == b => 0 a != a => 0 a != b => 1 a > b => 1 a < b => 0 a > 0.0 => 1 a < 0.0 => 0 b < 0.0 => 1 a + 10.0 = inf a - 10.0 = inf a * 10.0 = inf a / 10.0 = 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 が得られます。C++の出力関数は負のゼロを -0 と表示します。
IEEE 754 では、演算子 (C/C++ では ==) による 0.0 と -0.0 の比較は等しいと判定されます。数値の符号は標準ライブラリ <cmath> にある関数 signbit で求めることができます。signbit(x) は引数 x が負ならば真を返します。これで 0.0 と -0.0 を区別することができます。
リスト : 負のゼロ (mzero1.cpp) #include <iostream> #include <cmath> #include <limits> using namespace std; int main() { double a = -1e-323; double inf = numeric_limits<double>::infinity(); cout << "a = " << a << endl; cout << "a / 2 = " << a / 2.0 << endl; cout << "a / 4 = " << a / 4.0 << endl; cout << "1.0 / -inf = " << 1.0 / (- inf) << endl; cout << "-1.0 / inf = " << -1.0 / inf << endl; cout << "-1.0 * 0.0 = " << -1.0 * 0.0 << endl; double z1 = 0.0; double z2 = -0.0; cout << "z1 = " << z1 << endl; cout << "z2 = " << z2 << endl; cout << "z1 == z2 => " << (z1 == z2) << endl; cout << "z1 != z2 => " << (z1 != z2) << endl; cout << "signbit(z1) => " << signbit(z1) << endl; cout << "signbit(z2) => " << signbit(z2) << endl; }
$ clang++ mzero1.cpp $ ./a.out a = -9.88131e-324 a / 2 = -4.94066e-324 a / 4 = -0 1.0 / -inf = -0 -1.0 / inf = -0 -1.0 * 0.0 = -0 z1 = 0 z2 = -0 z1 == z2 => 1 z1 != z2 => 0 signbit(z1) => 0 signbit(z2) => 1
なお、-0.0 は数学関数 (cmath の関数 atan2 など) や複素数の演算処理などで使われます。
リスト : 負のゼロ (mzero2.cpp) #include <iostream> #include <cmath> using namespace std; int main() { cout << "sqrt(0.0) => " << sqrt(0.0) << endl; cout << "sqrt(-0.0) => " << sqrt(-0.0) << endl; cout << "atan2(0.0 << -1.0) => " << atan2(0.0, -1.0) << endl; cout << "atan2(-0.0 << -1.0) => " << atan2(-0.0, -1.0) << endl; }
$ clang++ mzero2.cpp $ ./a.out sqrt(0.0) => 0 sqrt(-0.0) => -0 atan2(0.0 << -1.0) => 3.14159 atan2(-0.0 << -1.0) => -3.14159
NaN は数ではないことを表す特別な値 (非数) です。一般的には 0.0 / 0.0 といった不正な演算を行うと、その結果は NaN になります。標準ライブラリ <limits> には NaN を取得する関数が定義されています。また、標準ライブラリ <cmath> には NaN を判定するマクロ isnan も用意されています。
リスト : 非数 (nantest.cpp) #include <iostream> #include <limits> #include <cmath> using namespace std; int main() { double nan = numeric_limits<double>::quiet_NaN(); double snan = numeric_limits<double>::signaling_NaN(); double inf = numeric_limits<double>::infinity(); cout << "NaN = " << nan << endl; cout << "sNaN = " << snan << endl; cout << "NaN / NaN = " << nan / nan << endl; cout << "inf / inf = " << inf / inf << endl; cout << "isnan(NaN) = " << isnan(nan) << endl; cout << "isnan(sNaN) = " << isnan(snan) << endl; cout << "isnan(inf) = " << isnan(inf) << endl; cout << "isfinite(NaN) = " << isfinite(nan) << endl; cout << "isfinite(sNaN) = " << isfinite(snan) << endl; cout << "isfinite(inf) = " << isfinite(inf) << endl; cout << "isfinite(1.234) = " << isfinite(1.234) << endl; }
$ clang++ nantest.cpp $ ./a.out NaN = nan sNaN = nan NaN / NaN = nan inf / inf = -nan isnan(NaN) = 1 isnan(sNaN) = 1 isnan(inf) = 0 isfinite(NaN) = 0 isfinite(sNaN) = 0 isfinite(inf) = 0 isfinite(1.234) = 1
数学では複素数 \(z\) を \(x + yi\) と表記します。x を実部、y を虚部、i を虚数単位といいます。虚数単位は 2 乗すると -1 になる数です。実部と虚部の 2 つの数値を格納するデータ構造を用意すれば、プログラミング言語でも複素数を表すことができます。
C++で複素数を扱うときはヘッダファイル <complex> をインクルードしてください。<complex> には複素数の計算に必要な定義や関数などが含まれています。
C++の場合、複素数はテンプレートクラス complex<T> として定義されています。型 T は double, float, long double を指定します。
complex<double>, 実部と虚部が double の数 complex<float>, 実部と虚部が float の数 complex<long double>, 実部と虚部が long double の数
g++ と clang++ は複素数 \(z = x + yi\) を x + yi と記述することができます。これは clang や gcc の拡張機能と同じです。規格 C++14 から導入された複素数を表すリテラル (i, if, il) を使うと、他のコンパイラでも同様の記述ができると思います。もちろん、コンストラクタでも複素数を生成することができます。
complex<T>(T real, T imag) => 複素数
第 1 引数 real に実部、第 2 引数 imag に虚部の数値を指定します。省略すると 0.0 になります。
複素数の実部はメンバ関数 real で、虚部はメンバ関数 imag で求めることができます。real と imag は関数版も用意されています。複素数の虚部の符号を反転することを「複素共役」といいます。複素共役は関数 conj で求めることができます。
簡単な例を示しましょう。
リスト : 複素数の簡単な使用例 (ctest1.cpp) #include <iostream> #include <complex> using namespace std; int main() { complex<double> a = 1.0 + 2.0i; complex<double> b = 3.0 - 4.0i; complex<double> c(-5.0, -6.0); cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; cout << "a.real() = " << a.real() << endl; cout << "a.imag() = " << a.imag() << endl; cout << "b.real() = " << b.real() << endl; cout << "b.imag() = " << b.imag() << endl; cout << "c.real() = " << c.real() << endl; cout << "c.imag() = " << c.imag() << endl; cout << "conj(a) = " << conj(a) << endl; cout << "conj(b) = " << conj(b) << endl; cout << "conj(c) = " << conj(c) << endl; }
$ clang++ ctest1.cpp $ ./a.out a = (1,2) b = (3,-4) c = (-5,-6) a.real() = 1 a.imag() = 2 b.real() = 3 b.imag() = -4 c.real() = -5 c.imag() = -6 conj(a) = (1,-2) conj(b) = (3,4) conj(c) = (-5,6)
複素数は極形式 \(z = r (\cos \theta + i \sin \theta)\) で表すことができます。このとき、r を絶対値、\(\theta\) を偏角といいます。絶対値は関数 abs で求めることができます。偏角は複素平面において正の実軸とベクトル (x, y) との角度を表します。偏角は関数 arg で求めることができます。返り値 \(\theta\) の範囲は \(-\pi \leq \theta \leq \pi\) (\(\pi\) : 円周率) です。
関数 polar は絶対値 r と偏角 \(\theta\) から複素数を生成します。
complex<T> polar(T r, T theta) => 複素数
第 1 引数 r が絶対値、第 2 引数 theat が偏角を表します。theta を省略すると 0.0 になります。
簡単な例を示しましょう。
リスト : 複素数の簡単な使用例 (ctest2.cpp) #include <iostream> #include <complex> using namespace std; int main() { double x[9] = {-1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, -1.0, -1.0}; double y[9] = { 0.0, 1.0, 1.0, 1.0, 0.0, -1.0, -1.0, -1.0, -0.0}; for (int n = 0; n < 9; n++) { complex<double> a(x[n], y[n]); cout << "a = " << a << endl; cout << "abs(a) = " << abs(a) << endl; cout << "arg(a) = " << arg(a) << endl; cout << "polar(abs(a), arg(a)) = " << polar(abs(a), arg(a)) << endl; } }
$ clang++ ctest2.cpp $ ./a.out a = (-1,0) abs(a) = 1 arg(a) = 3.14159 polar(abs(a), arg(a)) = (-1,1.22465e-16) a = (-1,1) abs(a) = 1.41421 arg(a) = 2.35619 polar(abs(a), arg(a)) = (-1,1) a = (0,1) abs(a) = 1 arg(a) = 1.5708 polar(abs(a), arg(a)) = (6.12323e-17,1) a = (1,1) abs(a) = 1.41421 arg(a) = 0.785398 polar(abs(a), arg(a)) = (1,1) a = (1,0) abs(a) = 1 arg(a) = 0 polar(abs(a), arg(a)) = (1,0) a = (1,-1) abs(a) = 1.41421 arg(a) = -0.785398 polar(abs(a), arg(a)) = (1,-1) a = (0,-1) abs(a) = 1 arg(a) = -1.5708 polar(abs(a), arg(a)) = (6.12323e-17,-1) a = (-1,-1) abs(a) = 1.41421 arg(a) = -2.35619 polar(abs(a), arg(a)) = (-1,-1) a = (-1,-0) abs(a) = 1 arg(a) = -3.14159 polar(abs(a), arg(a)) = (-1,-1.22465e-16)
C/C++ の場合、\(-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\) とします。
複素数の四則演算は次のようになります。
これらの演算はC++の演算子 +, -, * , / で行うことができます。複素数の場合、大小の比較演算は使えませんが、等値の判定は演算子 == や != で行うことができます。簡単な例を示しましょう。
リスト : 複素数の使用例 (ctest3.c) #include <iostream> #include <complex> using namespace std; int main() { complex<double> a(1.0, 2.0); complex<double> b(3.0, 4.0); cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "a + b = " << a + b << endl; cout << "a - b = " << a - b << endl; cout << "a * b = " << a * b << endl; cout << "a / b = " << a / b << endl; cout << "a == a => " << (a == a) << endl; cout << "a != a => " << (a != a) << endl; cout << "a == b => " << (a == b) << endl; cout << "a != b => " << (a != b) << endl; complex<double> c(1e300, 1e300); cout << "c = " << c << endl; cout << "abs(c) = " << abs(c) << endl; cout << "1 / c = " << 1.0 / c << endl; cout << "c * c = " << c * c << endl; return 0; }
$ clang++ ctest3.cpp a = (1,2) b = (3,4) a + b = (4,6) a - b = (-2,-2) a * b = (-5,10) a / b = (0.44,0.08) a == a => 1 a != a => 0 a == b => 0 a != b => 1 c = (1e+300,1e+300) abs(c) = 1.41421e+300 1 / c = (5e-301,-5e-301) c * c = (-nan,inf)
実部と虚部の値は inf や nan になることもあります。
複素数を引数にとる指数関数はオイラー (Euler) の公式から導くことができます。
複素数の対数関数は複素数 z を絶対値 \(|z|\) と偏角 \(\theta\) を使って導くことができます。
複素数 x, y のべき乗 \(x^y\) は次式で求めることができます。
標準ライブラリ <complex> には、数学関数をオーバーロードした複素数用の関数が定義されています。
指数関数: exp() 対数関数: log() 累乗関数: pow()
簡単な使用例を示します。
リスト : 複素数の指数関数、対数関数、べき乗 (ctest4.cpp) #include <iostream> #include <complex> using namespace std; #define PI 3.141592653589793 int main() { cout << "exp(0 + pi/4 i) = " << exp(complex<double>(0.0, PI / 4)) << endl; cout << "exp(0 + pi/2 i) = " << exp(complex<double>(0.0, PI / 2)) << endl; cout << "exp(0 + pi i) = " << exp(complex<double>(0.0, PI)) << endl; cout << "exp(0 - pi i) = " << exp(complex<double>(0.0,-PI)) << endl; cout << "exp(1 + i) = " << exp(1.0 + 1.0i) << endl; cout << "log(1 + i) = " << log(1.0 + 1.0i) << endl; cout << "log(1 + 0i) = " << log(1.0 + 0.0i) << endl; cout << "log(0 + i) = " << log(0.0 + 1.0i) << endl; cout << "log(1 - i) = " << log(1.0 - 1.0i) << endl; cout << "log(1e300 + 1e300 i) = " << log(1e300 + 1e300i) << endl; complex<double> a = 1.0 + 1.0i; cout << "pow(1+i, 0) = " << pow(a, 0.0) << endl; cout << "pow(1+i, 1) = " << pow(a, 1.0) << endl; cout << "pow(1+i, 2) = " << pow(a, 2.0) << endl; cout << "pow(1+i, 3) = " << pow(a, 3.0) << endl; cout << "pow(1+i, 1+i) = " << pow(a, a) << endl; cout << "pow(1+2i, 3+4i) = " << pow(1.0+2.0i, 3.0+4.0i) << endl; cout << "log(-1 + 0i) = " << log(-1.0 + 0.0i) << endl; cout << "log(-1 - 0i) = " << log(-1.0 - 0.0i) << endl; cout << "log(-1e300 + 0i) = " << log(-1e300 + 0.0i) << endl; cout << "log(-1e300 - 0i) = " << log(-1e300 - 0.0i) << endl; }
$ clang++ ctest4.cpp $ ./a.out exp(0 + pi/4 i) = (0.707107,0.707107) exp(0 + pi/2 i) = (6.12323e-17,1) exp(0 + pi i) = (-1,1.22465e-16) exp(0 - pi i) = (-1,-1.22465e-16) exp(1 + i) = (1.46869,2.28736) log(1 + i) = (0.346574,0.785398) log(1 + 0i) = (0,0) log(0 + i) = (0,1.5708) log(1 - i) = (0.346574,-0.785398) log(1e300 + 1e300 i) = (691.122,0.785398) pow(1+i, 0) = (1,0) pow(1+i, 1) = (1,1) pow(1+i, 2) = (1.22465e-16,2) pow(1+i, 3) = (-2,2) pow(1+i, 1+i) = (0.273957,0.583701) pow(1+2i, 3+4i) = (0.12901,0.0339241) log(-1 + 0i) = (0,3.14159) log(-1 - 0i) = (0,-3.14159) log(-1e300 + 0i) = (690.776,3.14159) log(-1e300 - 0i) = (690.776,-3.14159)
最後の 4 つの例を見てください。関数 \(\log z \ (z = x + iy)\) は負の実軸 (\(-\infty \lt x \lt 0\)) において、\(x + 0.0i\) と \(x - 0.0i\) では値が異なります。数学では値を一つ返す関数を「一価関数」、複数の値を返す関数を「多価関数」といいます。ここで、定義域を制限することで多価関数を一価関数にみなすことを考えます。関数 \(\log z\) の場合、負の実軸を定義域から取り除けば、\(\log z\) を一価関数とみなすことができるわけです。
参考 URL『双曲線関数と逆三角関数の branch cut | 雑記帳』によると、この取り除いた領域を branch cut と呼ぶそうです。プログラミングでは branch cut を定義域から取り除くのではなく、その領域では不連続な関数とするそうです。参考文献『COMMON LISP 第 2 版』では「分枝切断線」、Python のドキュメントでは「分枝切断」と記述されています。本稿では branch cut を「分枝切断」と記述することにします。
プログラミング言語の場合、0.0 と -0.0 を区別する処理系であれば、C言語のように 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] を使うと次の式が導かれます。
標準ライブラリ <complex> に定義されている三角関数 (sin, cos, tan) は、数学関数をオーバーロードして複素数に対応しています。簡単な使用例を示します。
リスト : 複素数の三角関数 (ctest5.cpp) #include <iostream> #include <complex> using namespace std; int main() { complex<double> c[4] = {0.0+1.0i, 0.0-1.0i, 1.0+1.0i, 1.0-1.0i}; for (int n = 0; n < 4; n++) { complex<double> a = c[n]; complex<double> b = sin(a); cout << "a = " << a << endl; cout << "sin(a) = " << b << endl; cout << "abs(sin(a)) = " << abs(b) << endl; } cout << endl; for (int n = 0; n < 4; n++) { complex<double> a = c[n]; complex<double> b = cos(a); cout << "a = " << a << endl; cout << "cos(a) = " << b << endl; cout << "abs(cos(a)) = " << abs(b) << endl; } cout << endl; for (int n = 0; n < 4; n++) { complex<double> a = c[n]; complex<double> b = tan(a); cout << "a = " << a << endl; cout << "tan(a) = " << b << endl; cout << "abs(tan(a)) = " << abs(b) << endl; } }
$ clang++ ctest5.cpp $ ./a.out a = (0,1) sin(a) = (0,1.1752) abs(sin(a)) = 1.1752 a = (0,-1) sin(a) = (0,-1.1752) abs(sin(a)) = 1.1752 a = (1,1) sin(a) = (1.29846,0.634964) abs(sin(a)) = 1.4454 a = (1,-1) sin(a) = (1.29846,-0.634964) abs(sin(a)) = 1.4454 a = (0,1) cos(a) = (1.54308,-0) abs(cos(a)) = 1.54308 a = (0,-1) cos(a) = (1.54308,0) abs(cos(a)) = 1.54308 a = (1,1) cos(a) = (0.83373,-0.988898) abs(cos(a)) = 1.29345 a = (1,-1) cos(a) = (0.83373,0.988898) abs(cos(a)) = 1.29345 a = (0,1) tan(a) = (0,0.761594) abs(tan(a)) = 0.761594 a = (0,-1) tan(a) = (0,-0.761594) abs(tan(a)) = 0.761594 a = (1,1) tan(a) = (0.271753,1.08392) abs(tan(a)) = 1.11747 a = (1,-1) tan(a) = (0.271753,-1.08392) abs(tan(a)) = 1.11747
複素数の双曲線関数の定義は、実数の定義で引数 x を複素数 z に変えたものになります。
sinh z, cosh z に純虚数 ix を与えると三角関数 (sin x, cos x) になります。
これに双曲線関数の加法定理を使うと、次の式が導かれます。
標準ライブラリ <complex> に定義されている双曲線関数 (sinh, cosh, tanh) は、数学関数をオーバーロードして複素数に対応しています。簡単な使用例を示します。
リスト : 複素数の双曲線関数 (ctest6.cpp) #include <iostream> #include <complex> using namespace std; int main() { complex<double> c[5] = {0.0+1.0i, 0.0-1.0i, 1.0+1.0i, 1.0-1.0i, 0.0+0.0i}; for (int n = 0; n < 5; n++) { complex<double> a = c[n]; complex<double> b = sinh(a); cout << "a = " << a << endl; cout << "sinh(a) = " << b << endl; } cout << endl; for (int n = 0; n < 5; n++) { complex<double> a = c[n]; complex<double> b = cosh(a); cout << "a = " << a << endl; cout << "cosh(a) = " << b << endl; } cout << endl; for (int n = 0; n < 5; n++) { complex<double> a = c[n]; complex<double> b = tanh(a); cout << "a = " << a << endl; cout << "tanh(a) = " << b << endl; } }
$ clang++ ctest6.cpp $ ./a.out a = (0,1) sinh(a) = (0,0.841471) a = (0,-1) sinh(a) = (0,-0.841471) a = (1,1) sinh(a) = (0.634964,1.29846) a = (1,-1) sinh(a) = (0.634964,-1.29846) a = (0,0) sinh(a) = (0,0) a = (0,1) cosh(a) = (0.540302,0) a = (0,-1) cosh(a) = (0.540302,-0) a = (1,1) cosh(a) = (0.83373,0.988898) a = (1,-1) cosh(a) = (0.83373,-0.988898) a = (0,0) cosh(a) = (1,0) a = (0,1) tanh(a) = (0,1.55741) a = (0,-1) tanh(a) = (0,-1.55741) a = (1,1) tanh(a) = (1.08392,0.271753) a = (1,-1) tanh(a) = (1.08392,-0.271753) a = (0,0) tanh(a) = (0,0)
複素数 z の平方根は上の式で求めることができます。式 (1) を平方根の主値といいます。角度は \(2\pi\) を足すと同じ角度になるので、式 (2) がもう一つの解になります。三角関数の半角の公式を使うと、式 (1) から次の式が導かれます。
標準ライブラリ <complex> に定義されている sqrt は、数学関数をオーバーロードして複素数に対応しています。簡単な実行例を示します。
リスト : 複素数の平方根 (ctest7.cpp) #include <iostream> #include <complex> using namespace std; int main() { complex<double> c[6] = {2.0+0.0i, -2.0+0.0i, 0.0+2.0i, 0.0-2.0i, 2.0+2.0i, -2.0-2.0i}; for (int n = 0; n < 6; n++) { complex<double> a = c[n]; complex<double> b = sqrt(a); cout << "a = " << a << endl; cout << "sqrt(a) = " << b << endl; cout << "sqrt(a) * sqrt(a) = " << b * b << endl; } cout << "sqrt(-2+0i) = " << sqrt(-2.0 + 0.0i) << endl; cout << "sqrt(-2-0i) = " << sqrt(-2.0 - 0.0i) << endl; cout << "sqrt(-1e300+0i) = " << sqrt(-1e300 + 0.0i) << endl; cout << "sqrt(-1e300-0i) = " << sqrt(-1e300 - 0.0i) << endl; }
$ clang++ ctest7.cpp $ ./a.out a = (2,0) sqrt(a) = (1.41421,0) sqrt(a) * sqrt(a) = (2,0) a = (-2,0) sqrt(a) = (0,1.41421) sqrt(a) * sqrt(a) = (-2,0) a = (0,2) sqrt(a) = (1,1) sqrt(a) * sqrt(a) = (0,2) a = (0,-2) sqrt(a) = (1,-1) sqrt(a) * sqrt(a) = (0,-2) a = (2,2) sqrt(a) = (1.55377,0.643594) sqrt(a) * sqrt(a) = (2,2) a = (-2,-2) sqrt(a) = (0.643594,-1.55377) sqrt(a) * sqrt(a) = (-2,-2) sqrt(-2+0i) = (0,1.41421) sqrt(-2-0i) = (0,-1.41421) sqrt(-1e300+0i) = (0,1e+150) sqrt(-1e300-0i) = (0,-1e+150)
\(\sqrt x\) は \(\log x\) と同じ分枝切断を持っています。x を負の整数とすると sqrt(x) の解は \(i \sqrt x\) になりますが、もうひとつ \(-i \sqrt x\) という解があります。
三角関数の逆関数を「逆三角関数 (inverse trigonometric function)」といいます。以下に標準ライブラリ <cmath> に定義されている逆三角関数を示します。
ここでは引数 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 と表示します。
簡単な実行例を示します。
リスト : 逆三角関数 (ctest8.cpp) #include <iostream> #include <cmath> using namespace std; int main() { for (double x = -1.0; x <= 1.0; x += 0.5) { printf("asin(%g) = %g\n", x, asin(x)); } for (double x = -1.0; x <= 1.0; x += 0.5) { printf("acos(%g) = %g\n", x, acos(x)); } for (double x = -2.0; x <= 2.0; x += 1.0) { printf("atan(%g) = %g\n", x, atan(x)); } }
$ clang++ ctest8.cpp $ ./a.out asin(-1) = -1.5708 asin(-0.5) = -0.523599 asin(0) = 0 asin(0.5) = 0.523599 asin(1) = 1.5708 acos(-1) = 3.14159 acos(-0.5) = 2.0944 acos(0) = 1.5708 acos(0.5) = 1.0472 acos(1) = 0 atan(-2) = -1.10715 atan(-1) = -0.785398 atan(0) = 0 atan(1) = 0.785398 atan(2) = 1.10715
cmath には関数 atan2 も用意されています。
atan2(y, x) => 角度
引数 x, y は実数です。atan2 は直交座標系においてベクトル (x, y) と x 軸との角度を求める関数です。複素平面で考えると、複素数 \(x + iy\) の偏角 \(\theta\) を求めることと同じです。返り値 (角度 \(\theta\)) の範囲は \(-\pi \leq \theta \leq \pi\) になります。
簡単な使用例を示します。
リスト : atan2 の使用例 (ctest81.cpp) #include <iostream> #include <cmath> using namespace std; int main(void) { double x[9] = {-1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, -1.0, -1.0}; double y[9] = { 0.0, 1.0, 1.0, 1.0, 0.0, -1.0, -1.0, -1.0, -0.0}; for (int n = 0; n < 9; n++) { printf("atan2(%g, %g) = %g\n", y[n], x[n], atan2(y[n], x[n])); } }
$ clang++ ctest81.cpp atan2(0, -1) = 3.14159 atan2(1, -1) = 2.35619 atan2(1, 0) = 1.5708 atan2(1, 1) = 0.785398 atan2(0, 1) = 0 atan2(-1, 1) = -0.785398 atan2(-1, 0) = -1.5708 atan2(-1, -1) = -2.35619 atan2(-0, -1) = -3.14159
複素数の逆三角関数の定義は、複素数の三角関数の定義から導くことができます。asin z の定義は次のようになります。
\(\arccos z, \arctan z\) は定義だけを示します。
asin, acos, atan は次に示す分枝切断を持っています。
標準ライブラリ <complex> に定義されている逆三角関数 (asin, acos, atan) は、数学関数をオーバーロードして複素数に対応しています。
簡単な実行例を示します。
リスト : 複素数の逆三角関数 (ctest9.cpp) #include <iostream> #include <complex> using namespace std; int main() { complex<double> c[6] = {2.0+0.0i, -2.0+0.0i, 0.0+2.0i, 0.0-2.0i, 2.0+2.0i, -2.0-2.0i}; for (int n = 0; n < 6; n++) { complex<double> a = c[n]; complex<double> b = asin(a); cout << "a = " << a << endl; cout << "asin(a) = " << b << endl; cout << "sin(asin(a)) = " << sin(b) << endl; } cout << endl; cout << "asin(1e300+0.0i) = " << asin(1e300 + 0.0i) << endl; cout << "asin(1e300-0.0i) = " << asin(1e300 - 0.0i) << endl; cout << "asin(-1e300+0.0i) = " << asin(-1e300 + 0.0i) << endl; cout << "asin(-1e300-0.0i) = " << asin(-1e300 - 0.0i) << endl; cout << endl; for (int n = 0; n < 6; n++) { complex<double> a = c[n]; complex<double> b = acos(a); cout << "a = " << a << endl; cout << "acos(a) = " << b << endl; cout << "cos(acos(a)) = " << cos(b) << endl; } cout << endl; cout << "acos(1e300+0.0i) = " << acos(1e300 + 0.0i) << endl; cout << "acos(1e300-0.0i) = " << acos(1e300 - 0.0i) << endl; cout << "acos(-1e300+0.0i) = " << acos(-1e300 + 0.0i) << endl; cout << "acos(-1e300-0.0i) = " << acos(-1e300 - 0.0i) << endl; cout << endl; for (int n = 0; n < 6; n++) { complex<double> a = c[n]; complex<double> b = atan(a); cout << "a = " << a << endl; cout << "atan(a) = " << b << endl; cout << "tan(atan(a)) = " << tan(b) << endl; } cout << endl; cout << "atan(0+2i) = " << atan(0.0 + 2.0i) << endl; cout << "atan(-0+2i) = " << atan(-0.0 + 2.0i) << endl; cout << "atan(0-4i) = " << atan(0.0 - 4.0i) << endl; cout << "atan(-0-4i) = " << atan(-0.0 - 4.0i) << endl; cout << endl; }
$ clang++ ctest9.cpp $ ./a.out a = (2,0) asin(a) = (1.5708,1.31696) sin(asin(a)) = (2,1.06058e-16) a = (-2,0) asin(a) = (-1.5708,1.31696) sin(asin(a)) = (-2,1.06058e-16) a = (0,2) asin(a) = (0,1.44364) sin(asin(a)) = (0,2) a = (0,-2) asin(a) = (0,-1.44364) sin(asin(a)) = (0,-2) a = (2,2) asin(a) = (0.754249,1.73432) sin(asin(a)) = (2,2) a = (-2,-2) asin(a) = (-0.754249,-1.73432) sin(asin(a)) = (-2,-2) asin(1e300+0.0i) = (1.5708,691.469) asin(1e300-0.0i) = (1.5708,-691.469) asin(-1e300+0.0i) = (-1.5708,691.469) asin(-1e300-0.0i) = (-1.5708,-691.469) a = (2,0) acos(a) = (0,-1.31696) cos(acos(a)) = (2,0) a = (-2,0) acos(a) = (3.14159,-1.31696) cos(acos(a)) = (-2,2.12115e-16) a = (0,2) acos(a) = (1.5708,-1.44364) cos(acos(a)) = (1.3692e-16,2) a = (0,-2) acos(a) = (1.5708,1.44364) cos(acos(a)) = (1.3692e-16,-2) a = (2,2) acos(a) = (0.816547,-1.73432) cos(acos(a)) = (2,2) a = (-2,-2) acos(a) = (2.32505,1.73432) cos(acos(a)) = (-2,-2) acos(1e300+0.0i) = (0,-691.469) acos(1e300-0.0i) = (0,691.469) acos(-1e300+0.0i) = (3.14159,-691.469) acos(-1e300-0.0i) = (3.14159,691.469) a = (2,0) atan(a) = (1.10715,0) tan(atan(a)) = (2,0) a = (-2,0) atan(a) = (-1.10715,0) tan(atan(a)) = (-2,0) a = (0,2) atan(a) = (1.5708,0.549306) tan(atan(a)) = (1.83697e-16,2) a = (0,-2) atan(a) = (1.5708,-0.549306) tan(atan(a)) = (1.83697e-16,-2) a = (2,2) atan(a) = (1.31122,0.238878) tan(atan(a)) = (2,2) a = (-2,-2) atan(a) = (-1.31122,-0.238878) tan(atan(a)) = (-2,-2) atan(0+2i) = (1.5708,0.549306) atan(-0+2i) = (1.5708,0.549306) // 実部の符号が逆 atan(0-4i) = (1.5708,-0.255413) atan(-0-4i) = (-1.5708,-0.255413)
C言語 (gcc, clang) の複素数と同じ結果になりました。C++ でも atan の分枝切断に不具合があるようです。
双曲線関数の逆関数を「逆双曲線関数 (inverse hyperbolic function)」といいます。標準ライブラリ <cmath> には逆双曲線関数 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\) を求めることができます。
簡単な実行例を示します。
リスト : 逆双曲線関数 (ctest10.cpp) #include <iostream> #include <cmath> using namespace std; int main() { for (double x = -2.0; x <= 2.0; x += 0.5) { printf("asinh(%g) = %g\n", x, asinh(x)); printf("sinh(asinh(%g)) = %g\n", x, sinh(asinh(x))); } printf("\n"); for (double x = 1.0; x <= 4.0; x += 0.5) { printf("acosh(%g) = %g\n", x, acosh(x)); printf("cosh(acosh(%g)) = %g\n", x, cosh(acosh(x))); } printf("\n"); for (double x = -0.75; x <= 0.75; x += 0.25) { printf("atanh(%g) = %g\n", x, atanh(x)); printf("tanh(atanh(%g)) = %g\n", x, tanh(atanh(x))); } }
$ clang++ ctest10.cpp $ ./a.out asinh(-2) = -1.44364 sinh(asinh(-2)) = -2 asinh(-1.5) = -1.19476 sinh(asinh(-1.5)) = -1.5 asinh(-1) = -0.881374 sinh(asinh(-1)) = -1 asinh(-0.5) = -0.481212 sinh(asinh(-0.5)) = -0.5 asinh(0) = 0 sinh(asinh(0)) = 0 asinh(0.5) = 0.481212 sinh(asinh(0.5)) = 0.5 asinh(1) = 0.881374 sinh(asinh(1)) = 1 asinh(1.5) = 1.19476 sinh(asinh(1.5)) = 1.5 asinh(2) = 1.44364 sinh(asinh(2)) = 2 acosh(1) = 0 cosh(acosh(1)) = 1 acosh(1.5) = 0.962424 cosh(acosh(1.5)) = 1.5 acosh(2) = 1.31696 cosh(acosh(2)) = 2 acosh(2.5) = 1.5668 cosh(acosh(2.5)) = 2.5 acosh(3) = 1.76275 cosh(acosh(3)) = 3 acosh(3.5) = 1.92485 cosh(acosh(3.5)) = 3.5 acosh(4) = 2.06344 cosh(acosh(4)) = 4 atanh(-0.75) = -0.972955 tanh(atanh(-0.75)) = -0.75 atanh(-0.5) = -0.549306 tanh(atanh(-0.5)) = -0.5 atanh(-0.25) = -0.255413 tanh(atanh(-0.25)) = -0.25 atanh(0) = 0 tanh(atanh(0)) = 0 atanh(0.25) = 0.255413 tanh(atanh(0.25)) = 0.25 atanh(0.5) = 0.549306 tanh(atanh(0.5)) = 0.5 atanh(0.75) = 0.972955 tanh(atanh(0.75)) = 0.75
複素数の逆双曲線関数の定義は、実数の定義で引数 x を複素数 z に変えたものになります。
参考 URL 『双曲線関数と逆三角関数の branch cut | 雑記帳』によると、\(\cosh^{-1} z\) を式 (1) でプログラムすると「分枝切断線」が複雑になるため、他の式 (たとえば (2) など) でプログラムする処理系が多いようです。ちなみに、ANSI Common Lisp では \(\cosh^{-1} z\) を次の式で定義しています。
asinh, acosh, atanh は次に示す分枝切断を持っています。
標準ライブラリ <complex> の逆双曲線関数 (asinh, acosh, atanh) は、数学関数をオーバーロードして複素数に対応しています。
簡単な実行例を示します。
リスト : 複素数の逆双曲線関数 (ctest11.cpp) #include <iostream> #include <complex> using namespace std; int main() { complex<double> c[6] = {2.0+0.0i, -2.0+0.0i, 0.0+2.0i, 0.0-2.0i, 2.0+2.0i, -2.0-2.0i}; for (int n = 0; n < 6; n++) { complex<double> a = c[n]; complex<double> b = asinh(a); cout << "a = " << a << endl; cout << "asinh(a) = " << b << endl; cout << "sinh(asinh(a)) = " << sinh(b) << endl; } cout << endl; cout << "asinh(0.0+2.0i) = " << asinh(0.0 + 2.0i) << endl; cout << "asinh(-0.0+2.0i) = " << asinh(-0.0 + 2.0i) << endl; cout << "asinh(0.0-4.0i) = " << asinh(0.0 - 4.0i) << endl; cout << "asinh(-0.0-4.0i) = " << asinh(-0.0 - 4.0i) << endl; cout << endl; for (int n = 0; n < 6; n++) { complex<double> a = c[n]; complex<double> b = acosh(a); cout << "a = " << a << endl; cout << "acosh(a) = " << b << endl; cout << "cosh(acosh(a)) = " << cosh(b) << endl; } cout << endl; cout << "acosh(0+0i) = " << acosh(0.0 + 0.0i) << endl; cout << "acosh(0-0i) = " << acosh(0.0 - 0.0i) << endl; cout << "acosh(-4+0i) = " << acosh(-4.0 + 0.0i) << endl; cout << "acosh(-4-0i) = " << acosh(-4.0 - 0.0i) << endl; cout << endl; for (int n = 0; n < 6; n++) { complex<double> a = c[n]; complex<double> b = atanh(a); cout << "a = " << a << endl; cout << "atanh(a) = " << b << endl; cout << "tanh(atanh(a)) = " << tanh(b) << endl; } cout << endl; cout << "atanh(2+0i) = " << atanh(2.0 + 0.0i) << endl; cout << "atanh(2-0i) = " << atanh(2.0 - 0.0i) << endl; cout << "atanh(-4+0i) = " << atanh(-4.0 + 0.0i) << endl; cout << "atanh(-4-0i) = " << atanh(-4.0 - 0.0i) << endl; cout << endl; }
$ clang++ ctest11.cpp $ ./a.out a = (2,0) asinh(a) = (1.44364,0) sinh(asinh(a)) = (2,0) a = (-2,0) asinh(a) = (-1.44364,0) sinh(asinh(a)) = (-2,0) a = (0,2) asinh(a) = (1.31696,1.5708) sinh(asinh(a)) = (1.06058e-16,2) a = (0,-2) asinh(a) = (1.31696,-1.5708) sinh(asinh(a)) = (1.06058e-16,-2) a = (2,2) asinh(a) = (1.73432,0.754249) sinh(asinh(a)) = (2,2) a = (-2,-2) asinh(a) = (-1.73432,-0.754249) sinh(asinh(a)) = (-2,-2) asinh(0.0+2.0i) = (1.31696,1.5708) asinh(-0.0+2.0i) = (1.31696,1.5708) // 実部の符号が逆 asinh(0.0-4.0i) = (2.06344,-1.5708) asinh(-0.0-4.0i) = (-2.06344,-1.5708) a = (2,0) acosh(a) = (1.31696,0) cosh(acosh(a)) = (2,0) a = (-2,0) acosh(a) = (1.31696,3.14159) cosh(acosh(a)) = (-2,2.12115e-16) a = (0,2) acosh(a) = (1.44364,1.5708) cosh(acosh(a)) = (1.3692e-16,2) a = (0,-2) acosh(a) = (1.44364,-1.5708) cosh(acosh(a)) = (1.3692e-16,-2) a = (2,2) acosh(a) = (1.73432,0.816547) cosh(acosh(a)) = (2,2) a = (-2,-2) acosh(a) = (1.73432,-2.32505) cosh(acosh(a)) = (-2,-2) acosh(0+0i) = (0,1.5708) acosh(0-0i) = (0,-1.5708) acosh(-4+0i) = (2.06344,3.14159) acosh(-4-0i) = (2.06344,-3.14159) a = (2,0) atanh(a) = (0.549306,1.5708) tanh(atanh(a)) = (2,1.83697e-16) a = (-2,0) atanh(a) = (-0.549306,1.5708) tanh(atanh(a)) = (-2,1.83697e-16) a = (0,2) atanh(a) = (0,1.10715) tanh(atanh(a)) = (0,2) a = (0,-2) atanh(a) = (0,-1.10715) tanh(atanh(a)) = (0,-2) a = (2,2) atanh(a) = (0.238878,1.31122) tanh(atanh(a)) = (2,2) a = (-2,-2) atanh(a) = (-0.238878,-1.31122) tanh(atanh(a)) = (-2,-2) atanh(2+0i) = (0.549306,1.5708) atanh(2-0i) = (0.549306,-1.5708) atanh(-4+0i) = (-0.255413,1.5708) atanh(-4-0i) = (-0.255413,-1.5708)
C言語 (gcc, clang) の複素数と同じ結果になりました。C++ でも asinh の分枝切断に不具合があるようです。