Complex — 複素数

概要

Complex<T> は、任意のスカラー型 T に対する複素数クラスである。 Tdouble, float, Int, Float, Rational など、 ComplexScalar コンセプトを満たす型であれば何でもよい。

  • 多倍長対応std::complex とは異なる独自実装で、多倍長数値型もテンプレート引数に取れる
  • Strassen 3乗算法 — 乗算を 3 回の実数乗算で行い、多倍長演算で高速化を実現する
  • Smith 除算T = double/float ではオーバーフローを回避する Smith の方法を使用する
  • std::complex 互換 — 変換コンストラクタ・暗黙変換演算子を備え、相互変換が可能である
  • SIMD 互換レイアウトstatic_assert により re, im の連続配置を保証する。reinterpret_caststd::complex<T> と同一メモリとして扱える

ComplexScalar コンセプトは以下の演算を要求する:

template<typename T>
concept ComplexScalar = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
    { a - b } -> std::convertible_to<T>;
    { a * b } -> std::convertible_to<T>;
    { a / b } -> std::convertible_to<T>;
    { -a }    -> std::convertible_to<T>;
    { T(0) };
    { T(1) };
};

ビルド

#include <math/core/Complex.hpp>

ヘッダオンリーである。CMake やリンクライブラリは不要。 Complex.hpp<complex>, <cmath>, <concepts> 等の標準ヘッダのみに依存する。

コンストラクタ

シグネチャ説明
Complex() デフォルトコンストラクタ。実部・虚部ともに T(0) で初期化する。
Complex(const T& real) 実数から構築する。虚部は T(0) になる。暗黙変換を許可する。
Complex(const T& real, const T& imag) 実部と虚部を指定して構築する。
explicit Complex(const Complex<U>& other) 異なるスカラー型 U からの変換。明示的変換 (explicit) が必要である。
Complex(const std::complex<U>& sc) std::complex からの変換。U が浮動小数点型かつ T に変換可能な場合のみ有効である。

アクセサ

メンバ / メソッド説明
T re 実部を直接参照する public メンバ変数。
T im 虚部を直接参照する public メンバ変数。
const T& real() const 実部への const 参照を返す。
T& real() 実部への参照を返す (書き換え可)。
void real(const T& val) 実部を val に設定する。
const T& imag() const 虚部への const 参照を返す。
T& imag() 虚部への参照を返す (書き換え可)。
void imag(const T& val) 虚部を val に設定する。
T normSq() const $|z|^2 = \mathrm{re}^2 + \mathrm{im}^2$ を返す。
std::string toString() const 文字列表現を返す (例: "3+4i", "-2i")。

re / im メンバは public であり、直接アクセスが可能である。 .real() / .imag() メソッドは std::complex との API 互換のために提供している。

算術演算子

Complex 同士の演算

演算子説明
z + w 複素数の加算。成分ごとに加算する。
z - w 複素数の減算。成分ごとに減算する。
z * w 複素数の乗算。T が浮動小数点型 (double/float) の場合は通常の 4 乗算 + 2 加減算を使用する。多倍長型 (Int, Float 等) では Strassen 3乗算法 (3 乗算 + 5 加減算) を使用し、乗算コストが支配的な場合に高速化する。
z / w 複素数の除算。T が浮動小数点型の場合は Smith の方法を使用し、中間値のオーバーフローを回避する。多倍長型では素朴な方法 ($\mathrm{denom} = c^2 + d^2$) を使用する。ゼロ除算時は NaN を返す。
+z, -z 単項プラス (恒等)、単項マイナス (符号反転)。

スカラー混合演算

Complex<T>T の間の四則演算をサポートする。左辺・右辺いずれにスカラーが来ても動作する。

演算子説明
z + s, s + z実部にスカラーを加算する。
z - s, s - z実部からスカラーを減算する / スカラーから複素数を減算する。
z * s, s * z実部・虚部それぞれにスカラーを乗算する。
z / s実部・虚部それぞれをスカラーで除算する。
s / zスカラーを複素数で除算する。浮動小数点型では Smith の方法を使用する。

複合代入演算子

+=, -=, *=, /= を Complex 同士およびスカラーとの間でサポートする。

比較演算子

演算子説明
z == w 実部と虚部がそれぞれ等しい場合に true を返す。浮動小数点型では厳密比較であることに注意すること。
z != w == の否定を返す。
z == s, s == z 実部がスカラーに等しく、虚部がゼロの場合に true を返す。
z != s, s != z 上記の否定を返す。

順序比較 (<, > 等) は複素数では数学的に定義されないため、提供しない。

数学関数

すべて sangi 名前空間の自由関数として提供する。ADL (Argument Dependent Lookup) により、名前空間を明示せずに呼び出せる。

基本関数

関数シグネチャ説明
abs(z) T abs(const Complex<T>&) 絶対値 $|z| = \sqrt{a^2 + b^2}$ を返す。T が浮動小数点型の場合は std::hypot を使用し、オーバーフローを回避する。
arg(z) T arg(const Complex<T>&) 偏角 $\mathrm{arg}(z) = \mathrm{atan2}(b, a)$ を返す。戻り値は $(-\pi, \pi]$ の範囲である。
conj(z) Complex<T> conj(const Complex<T>&) 共役複素数 $\bar{z} = a - bi$ を返す。
norm(z) T norm(const Complex<T>&) ノルムの二乗 $|z|^2 = a^2 + b^2$ を返す。STL 互換の名前である (std::norm と同じ意味)。
normSq(z) T normSq(const Complex<T>&) norm(z) と同一。名前がより明示的である。
real(z) const T& real(const Complex<T>&) 実部を返す。
imag(z) const T& imag(const Complex<T>&) 虚部を返す。
proj(z) Complex<T> proj(const Complex<T>&) リーマン球面への射影を返す。実部または虚部が無限大の場合、$(\infty, \pm 0)$ に写像する。有限の値はそのまま返す。

極形式

関数シグネチャ説明
polar(r, theta) Complex<T> polar(const T&, const T&) 極形式 $r e^{i\theta} = r\cos\theta + ir\sin\theta$ から構築する。
expI(omega) Complex<T> expI(const T&) $e^{i\omega} = \cos\omega + i\sin\omega$ を返す。FFT の回転因子等に便利である。

指数・対数・冪乗

関数シグネチャ説明
exp(z) Complex<T> exp(const Complex<T>&) $e^z = e^a(\cos b + i\sin b)$ を返す。
log(z) Complex<T> log(const Complex<T>&) 自然対数 $\ln z = \ln|z| + i\,\mathrm{arg}(z)$ を返す。主値 (虚部 $\in (-\pi, \pi]$) を返す。
log10(z) Complex<T> log10(const Complex<T>&) 常用対数 $\log_{10} z = \ln z \cdot \log_{10} e$ を返す。多倍長型では T::log10e() のキャッシュ済み定数を利用する。
sqrt(z) Complex<T> sqrt(const Complex<T>&) 主値の平方根を返す。実部が非負の分枝を選択する。
pow(z, n) Complex<T> pow(const Complex<T>&, int) 整数冪 $z^n$ を二進冪乗法で計算する。負の $n$ では逆数を先に求める。
pow(z, w) Complex<T> pow(const Complex<T>&, const Complex<T>&) 複素冪 $z^w = e^{w \ln z}$ を返す。$z = 0$ の場合は $0$ を返す。
pow(z, a) Complex<T> pow(const Complex<T>&, const T&) 実数冪 $z^a = e^{a \ln z}$ を返す。

三角関数

関数定義
sin(z)$\sin(a+bi) = \sin a \cosh b + i \cos a \sinh b$
cos(z)$\cos(a+bi) = \cos a \cosh b - i \sin a \sinh b$
tan(z)$\tan z = \sin z / \cos z$

双曲線関数

関数定義
sinh(z)$\sinh(a+bi) = \sinh a \cos b + i \cosh a \sin b$
cosh(z)$\cosh(a+bi) = \cosh a \cos b + i \sinh a \sin b$
tanh(z)$\tanh z = \sinh z / \cosh z$

逆三角関数

関数定義
asin(z)$\arcsin z = -i \ln(iz + \sqrt{1 - z^2})$
acos(z)$\arccos z = -i \ln(z + i\sqrt{1 - z^2})$
atan(z)$\arctan z = \frac{1}{2i} \ln\frac{1+iz}{1-iz}$

逆双曲線関数

関数定義
asinh(z)$\mathrm{asinh}\,z = \ln(z + \sqrt{z^2 + 1})$
acosh(z)$\mathrm{acosh}\,z = \ln(z + \sqrt{z^2 - 1})$
atanh(z)$\mathrm{atanh}\,z = \frac{1}{2}\ln\frac{1+z}{1-z}$

ユーザー定義リテラル

using sangi::literals; を宣言すると、以下のリテラルが使用可能になる。

リテラル
_i Complex<double> 3.0_i → $0 + 3i$、3_i → $0 + 3i$
_if Complex<float> 3.0_ifComplex<float>(0, 3)
using sangi::literals;

auto z = 2.0 + 3.0_i;   // Complex<double>(2, 3)
auto w = 1.0 - 0.5_i;   // Complex<double>(1, -0.5)
auto f = 1.0_if + 2.0_if; // Complex<float>(0, 3)

std::complex との互換性

変換

T が浮動小数点型 (float, double, long double) の場合、以下の変換が暗黙的に行われる。

// std::complex → Complex
std::complex<double> sc(1.0, 2.0);
Complex<double> z = sc;     // 暗黙変換

// Complex → std::complex
Complex<double> w(3.0, 4.0);
std::complex<double> sw = w; // 暗黙変換

メモリレイアウト互換

Complex<T>re, im がメモリ上で連続的に配置されることを static_assert で保証している。 したがって、Complex<double> の配列を std::complex<double> の配列として reinterpret_cast で再解釈できる。

// 配列の相互変換
std::vector<Complex<double>> data(1024);
auto* std_ptr = reinterpret_cast<std::complex<double>*>(data.data());
// FFTW 等の C ライブラリにそのまま渡せる

以下の static_assert により、レイアウトの正しさがコンパイル時に検証される:

  • sizeof(Complex<T>) == 2 * sizeof(T)
  • offsetof(Complex<T>, re) == 0
  • offsetof(Complex<T>, im) == sizeof(T)

auto と式テンプレート

Complex<T> 自体は式テンプレートを使用しない。 すべての演算子は Complex<T> を直接返すため、auto で受け取っても問題ない。

auto z = a + b * c;  // OK: Complex<T> 型

ただし、Matrix<Complex<T>> のように式テンプレートを使用するコンテナの内部で Complex<T> を要素型として使う場合は、 行列演算の結果を auto で受け取ると式テンプレートのプロキシ型になる可能性がある。 その場合は .eval() を呼ぶか、明示的に型を指定すること。

Matrix<Complex<double>> A, B;
auto expr = A * B;                           // プロキシ型の可能性あり
Matrix<Complex<double>> C = A * B;          // 確実に評価される
auto D = (A * B).eval();                     // eval() で明示的に評価

コード例

#include <math/core/Complex.hpp>
#include <iostream>
using namespace sangi;
using namespace sangi::literals;

int main() {
    // 基本的な構築
    Complex<double> z1(3.0, 4.0);   // 3 + 4i
    Complex<double> z2 = 2.0 + 1.0_i; // 2 + i

    // 四則演算
    auto sum  = z1 + z2;   // 5 + 5i
    auto prod = z1 * z2;   // (3*2 - 4*1) + (3*1 + 4*2)i = 2 + 11i
    auto quot = z1 / z2;   // Smith の方法で計算

    // 数学関数
    std::cout << "abs(z1)  = " << abs(z1)  << std::endl;  // 5
    std::cout << "arg(z1)  = " << arg(z1)  << std::endl;  // atan2(4,3)
    std::cout << "conj(z1) = " << conj(z1) << std::endl;  // 3-4i

    // 極形式
    auto w = polar(5.0, 0.9273);  // ≈ 3 + 4i

    // 指数・対数
    auto e_iz = exp(Complex<double>(0.0, M_PI));  // ≈ -1 + 0i (Euler の公式)
    std::cout << "e^(i*pi) = " << e_iz << std::endl;

    // std::complex との相互変換
    std::complex<double> sc(1.0, 2.0);
    Complex<double> from_std = sc;
    std::complex<double> to_std = from_std;

    // expI: FFT 回転因子
    int N = 8;
    for (int k = 0; k < N; ++k) {
        auto twiddle = expI(-2.0 * M_PI * k / N);
        std::cout << "W_" << N << "^" << k << " = " << twiddle << std::endl;
    }
}