M.Hiroi's Home Page

Linux Programming

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

[ PrevPage | C++ | NextPage ]

複素数

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

●C++の数

C言語と同様にC++は標準で「整数 (integer)」と「浮動小数点数 (floating point number)」を使うことができます。下表に基本的な数を示します。

表 : 基本的な数 (64 ビット処理系 [clang] の場合)
型名範囲
char-128 ~ 127 (1 バイト)
unsigned char0 ~ 255 (1 バイト)
short int-32768 ~ 32767 (2 バイト)
unsigned short int0 ~ 65535 (2 バイト)
int-2147483648 ~ 2147483647 (4 バイト)
unsigned int0 ~ 4294967295 (4 バイト)
long int-9223372036854775808 ~ 9223372036854775807 (8 バイト)
unsigned long int0 ~ 18446744073709551615 (8 バイト)
long long int-9223372036854775808 ~ 9223372036854775807 (8 バイト)
unsigned long long int0 ~ 18446744073709551615 (8 バイト)
float1.1754944E-38 ~ 3.4028235E+38 (32 bit 単精度)
double2.22507E-308 ~ 1.79769E+308 (64 bit 倍精度)
long double3.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

●C++の複素数

数学では複素数 \(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\) とします。

●複素数の四則演算

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

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

これらの演算は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) の公式から導くことができます。

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

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

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

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

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

標準ライブラリ <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 8 によると、この取り除いた領域を branch cut と呼ぶそうです。プログラミングでは branch cut を定義域から取り除くのではなく、その領域では不連続な関数とするそうです。参考文献 1 では「分枝切断線」、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 -\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}\)

標準ライブラリ <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
-- 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}\)

標準ライブラリ <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 の平方根は次の式で求めることができます。

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

標準ライブラリ <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 は無数に存在します。つまり、逆三角関数の返り値は無数にあることになりますが、通常は一つの値を返すように範囲を制限します。これを「主値」といいます。

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

\(\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 と表示します。

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

リスト : 逆三角関数 (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 の定義は次のようになります。

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

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

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

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

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

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

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

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

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

標準ライブラリ <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 \) で表します。双曲線関数と逆双曲線関数の定義域と値域を示します。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

リスト : 逆双曲線関数 (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 に変えたものになります。

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

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

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

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

標準ライブラリ <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 の分枝切断に不具合があるようです。

●参考文献, 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 | 雑記帳

初版 2022 年 3 月 5 日
改訂 2023 年 4 月 15 日

Copyright (C) 2022-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | C++ | NextPage ]