Complex 初等関数と分枝切断

概要

sangi の Complex<T> は exp, log, 三角・双曲線関数とその逆関数、冪乗、$n$ 乗根を提供する。 いずれも主値 (principal value) を返し、分枝切断 (branch cut) の取り方は std::complex および Python cmath と同じ規約に従う。

実装はすべて T 側の実数版三角・指数関数を呼び出して構成され、 T = double では <cmath> の関数、 多倍長型では T::sin, T::cos, T::exp, T::log 等を呼ぶ。 多倍長型の精度は内部スカラー T の精度に従う。

API の一覧については Complex API リファレンス — 数学関数 を参照。

exp と log

exp

複素指数関数はオイラーの公式から直接構成する。

$$e^{a+bi} = e^a (\cos b + i \sin b)$$

実装は実数版 exp(a) を 1 回、cos(b)sin(b) を各 1 回呼び出すだけである。 $|b|$ が大きい場合の精度劣化は内部三角関数の引数縮約 (range reduction) の品質に依存する。

log — 主値と分枝切断

複素対数は本来多価関数

$$\log z = \ln |z| + i(\arg z + 2\pi k), \quad k \in \mathbb{Z}$$

であるが、sangi は主値 ($k = 0$) を返す。 主値の虚部は $(-\pi, \pi]$ に入る。

$$\operatorname{Log}(z) = \ln |z| + i \arg(z), \quad \arg(z) \in (-\pi, \pi]$$

分枝切断は argatan2 の規約に従うため負の実軸 ($a < 0$, $b = 0$) に置かれる。 負の実軸を $b \to 0^+$ で渡る場合の極限は虚部 $+\pi$、 $b \to 0^-$ で渡る場合は虚部 $-\pi$ となる。

log10

常用対数は底変換で計算する。

$$\log_{10} z = \log z \cdot \log_{10} e$$

多倍長型では T::log10e() のキャッシュ済み定数を使い、毎回の底変換コストを抑える。

sqrt と nth-root

sqrt

主値 $\sqrt{z}$ は実部が非負の枝を選ぶ。

$$\sqrt{z} = \sqrt{|z|} \, e^{i \arg(z) / 2}, \quad \arg(z) / 2 \in (-\pi/2, \pi/2]$$

分枝切断は log と同じく負の実軸に置かれる。 $a < 0$, $b \to 0^+$ では $\sqrt{z} \to i \sqrt{|a|}$、 $b \to 0^-$ では $\sqrt{z} \to -i \sqrt{|a|}$ となる。

実装は $r = \sqrt{|z|}$ と $\varphi = \arg(z) / 2$ を計算して polar(r, φ) を返す経路である。 実部の符号は $\cos\varphi$ から自動的に非負側になる。

n 乗根

複素数の $n$ 乗根は $n$ 個ある。

$$z^{1/n} = r^{1/n} \exp\left(i \frac{\varphi + 2\pi k}{n}\right), \quad k = 0, 1, \ldots, n-1$$

$r = |z|$, $\varphi = \arg(z)$。 $k = 0$ の値が主値であり、残りの $n-1$ 個は 単位円上で $2\pi/n$ 等間隔に分布する。 全 $n$ 個の根は正 $n$ 角形をなし、巡回群 $D_n$ の構造を持つ。

典型的な用途

  • 多項式方程式 $z^n = w$ の解を全列挙する
  • FFT における長さ $n$ の単位根 (これは $w = 1$, $r = 1$, $\varphi = 0$ の特殊形)
  • 有限体・代数体上の同様の構造との橋渡し

三角関数

複素三角関数は実部・虚部に対する加法定理から導かれる。

$$\begin{aligned} \sin(a+bi) &= \sin a \cosh b + i \cos a \sinh b, \\ \cos(a+bi) &= \cos a \cosh b - i \sin a \sinh b, \\ \tan z &= \frac{\sin z}{\cos z}. \end{aligned}$$

$|b|$ が大きい場合は $\cosh b$, $\sinh b$ がオーバーフローしうるため、 浮動小数点型では $b$ の符号で場合分けして $e^{|b|}$ の指数を打ち消す処理が 内部 cosh / sinh 側に委ねられる。

$\tan z$ は $\cos z = 0$ で極を持つ。 実数軸上では $z = \pi/2 + k\pi$ で発散する。

双曲線関数

双曲線関数も加法定理から導出する。

$$\begin{aligned} \sinh(a+bi) &= \sinh a \cos b + i \cosh a \sin b, \\ \cosh(a+bi) &= \cosh a \cos b + i \sinh a \sin b, \\ \tanh z &= \frac{\sinh z}{\cosh z}. \end{aligned}$$

三角関数と双曲線関数は次の対応で互いに変換できる:

$$\sinh(iz) = i \sin z, \quad \cosh(iz) = \cos z, \quad \tanh(iz) = i \tan z$$

逆三角関数

逆三角関数は対数を用いた閉形式で実装する。 いずれも sqrtlog の分枝切断を引き継ぐ。

$$\begin{aligned} \arcsin z &= -i \log\!\left(i z + \sqrt{1 - z^2}\right), \\ \arccos z &= -i \log\!\left(z + i\sqrt{1 - z^2}\right), \\ \arctan z &= \frac{1}{2 i} \log\!\frac{1 + i z}{1 - i z}. \end{aligned}$$

分枝切断:

  • $\arcsin z, \arccos z$ — 実軸上 $|x| > 1$ の 2 線分
  • $\arctan z$ — 虚軸上 $|y| > 1$ の 2 線分

これは std::asin / std::acos / std::atan および Python cmath と同じ規約である。

逆双曲線関数

$$\begin{aligned} \operatorname{asinh} z &= \log\!\left(z + \sqrt{z^2 + 1}\right), \\ \operatorname{acosh} z &= \log\!\left(z + \sqrt{z^2 - 1}\right), \\ \operatorname{atanh} z &= \frac{1}{2} \log\!\frac{1 + z}{1 - z}. \end{aligned}$$

分枝切断:

  • $\operatorname{asinh} z$ — 虚軸上 $|y| > 1$
  • $\operatorname{acosh} z$ — 実軸上 $x < 1$
  • $\operatorname{atanh} z$ — 実軸上 $|x| > 1$

逆三角関数同様、std::complex および Python cmath と同じ規約を採用している。

冪乗 pow

整数冪 pow(z, n)

$n$ が整数の場合は二進冪乗法 (square-and-multiply) で計算する。

$$z^n = \prod_{k : n_k = 1} z^{2^k}$$

$|n|$ ビット数だけのループで完了し、$O(\log|n|)$ 回の乗算で済む。 負の $n$ では先に $1/z$ を求めてから正の指数で計算する。 整数冪は分枝切断を持たないため、$z$ の分枝に関わらず連続な値が得られる。

複素冪 pow(z, w)

$w$ が複素数の場合は対数経由で計算する。

$$z^w = e^{w \log z}$$

$\log z$ の分枝切断 (負の実軸) がそのまま $z^w$ に転移する。 たとえば $(-1)^{1/2}$ は分枝切断の上側から近づくと $i$、下側からは $-i$ を返す。 $z = 0$ の場合は $w$ の値に関わらず $0$ を返す ($0^0$ も $0$ として扱う)。

実数冪 pow(z, a)

$a$ が実スカラーの場合も $e^{a \log z}$ で計算する。 ただし $a$ が整数値であっても浮動小数点では小数として扱われるため、 厳密な整数冪が欲しい場合は明示的に intpow(z, n) を呼ぶ。

std::complex / cmath との互換性

すべての関数は T = double の場合 std::complex<double> と bit-exact ではなく 規約レベルで等価な値を返す。 bit-exact でない理由は内部実装 (Kahan 加算の有無、内蔵 sin/cos の選択等) に依存するためである。 実用上問題になることはほとんど無いが、テストで bit 比較する場合は注意すること。

分枝切断・主値範囲・特殊値 (NaN, ±∞) の扱いは ISO C++ および Python cmath モジュールと同じ規約を採用している。 以下は典型的な特殊値:

入力出力
log(0)$-\infty + 0i$
log(-1 + 0i)$0 + \pi i$ (上側極限)
sqrt(-1 + 0i)$0 + i$ (上側極限)
pow(0, 0)$0$ (規約)
exp(NaN + ai)NaN

設計判断

主値を返す理由

複素対数・複素冪・複素 $n$ 乗根は多価関数だが、 プログラム言語の関数として実装する以上、単一値を返す必要がある。 どの値を返すかの規約は処理系ごとに異なる可能性があるが、 ISO C++ std::complex と Python cmath同じ主値規約を採用している。 sangi もこの規約に従うことで、相互運用と移植性を確保している。

多倍長型での精度

多倍長型 (Float) では計算精度を実行時に指定できる。 Complex<Float> の関数は内部 Float の精度をそのまま継承する。 三角関数・指数関数の引数縮約 (range reduction) は Float 側で $\pi$ や $\log 2$ のキャッシュ済み定数を用いて高精度に行われるため、 精度劣化なく任意精度の関数値が得られる。

整数冪と複素冪の経路分離

整数冪 pow(z, n) は二進冪乗法を使い、分枝切断を経由しない。 複素冪 pow(z, w) は対数経由で計算し、分枝切断の影響を受ける。 $n = 2$ や $n = -1$ のような頻出する小整数指数では、 前者を明示的に呼ぶことで分枝切断の罠を避けつつ高速化できる。

参考文献

  • Kahan, W. (1987). "Branch Cuts for Complex Elementary Functions, or Much Ado about Nothing's Sign Bit". In The State of the Art in Numerical Analysis, Oxford University Press, 165–211.
  • ISO/IEC 14882:2020 (C++20), §26.4 Complex numbers.
  • Python Software Foundation. The Python Standard Library — cmath. (主値・分枝規約の参照)