// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // Complex.hpp // Complex-number template class // // Features: // - Works with any numeric type T = double, float, Int, Float, Rational, ... // - Custom implementation distinct from std::complex (multi-precision compatible) // - 3-multiplication complex product via Strassen's method // - Type constraints via C++23 concepts // - T = double/float: Smith's division, hypot-based abs, NaN/Inf propagation (2026-03-20) #pragma once #include #include #include #include #include #include #include #include #include namespace sangi { // Constraints required on the element type of Complex template concept ComplexScalar = requires(T a, T b) { { a + b } -> std::convertible_to; { a - b } -> std::convertible_to; { a * b } -> std::convertible_to; { a / b } -> std::convertible_to; { -a } -> std::convertible_to; { T(0) }; { T(1) }; }; // ================================================================ // Complex class // ================================================================ template class Complex { public: T re; // real part T im; // imaginary part // ============================================================ // Constructors // ============================================================ // Default: zero-initialize constexpr Complex() noexcept(noexcept(T(0))) : re(T(0)), im(T(0)) {} // From a real number (imag = 0) constexpr Complex(const T& real) noexcept(noexcept(T(0))) : re(real), im(T(0)) {} // Real and imaginary parts constexpr Complex(const T& real, const T& imag) noexcept : re(real), im(imag) {} // Conversion from a different element type template constexpr explicit Complex(const Complex& other) : re(T(other.re)), im(T(other.im)) {} // Conversion from std::complex (T = double/float/long double) template requires std::is_floating_point_v && std::is_convertible_v constexpr Complex(const std::complex& sc) : re(T(sc.real())), im(T(sc.imag())) {} // Conversion to std::complex (T = double/float/long double) template requires std::is_floating_point_v constexpr operator std::complex() const { return std::complex(static_cast(re), static_cast(im)); } // ============================================================ // Accessors // ============================================================ [[nodiscard]] constexpr const T& real() const noexcept { return re; } [[nodiscard]] constexpr const T& imag() const noexcept { return im; } [[nodiscard]] constexpr T& real() noexcept { return re; } [[nodiscard]] constexpr T& imag() noexcept { return im; } constexpr void real(const T& val) { re = val; } constexpr void imag(const T& val) { im = val; } // ============================================================ // Comparison operators // ============================================================ [[nodiscard]] constexpr bool operator==(const Complex& rhs) const { return re == rhs.re && im == rhs.im; } [[nodiscard]] constexpr bool operator!=(const Complex& rhs) const { return !(*this == rhs); } // Comparison with a real number [[nodiscard]] constexpr bool operator==(const T& rhs) const { return re == rhs && im == T(0); } [[nodiscard]] constexpr bool operator!=(const T& rhs) const { return !(*this == rhs); } friend constexpr bool operator==(const T& lhs, const Complex& rhs) { return rhs == lhs; } friend constexpr bool operator!=(const T& lhs, const Complex& rhs) { return !(rhs == lhs); } // ============================================================ // Unary operators // ============================================================ [[nodiscard]] constexpr Complex operator+() const { return *this; } [[nodiscard]] constexpr Complex operator-() const { return Complex(-re, -im); } // ============================================================ // Complex + complex // ============================================================ [[nodiscard]] constexpr Complex operator+(const Complex& rhs) const { return Complex(re + rhs.re, im + rhs.im); } [[nodiscard]] constexpr Complex operator-(const Complex& rhs) const { return Complex(re - rhs.re, im - rhs.im); } // Floating-point: ordinary 4 multiplications + 2 additions/subtractions (similar cost) // Multi-precision: Strassen's method, 3 multiplications + 5 additions/subtractions (faster because multiplies are expensive) [[nodiscard]] constexpr Complex operator*(const Complex& rhs) const { if constexpr (std::is_floating_point_v) { return Complex(re * rhs.re - im * rhs.im, re * rhs.im + im * rhs.re); } else { T ac = re * rhs.re; T bd = im * rhs.im; return Complex(ac - bd, (re + im) * (rhs.re + rhs.im) - ac - bd); } } // Division via Smith's method (overflow-safe for T = double/float) // For multi-precision types, keep the naive implementation [[nodiscard]] constexpr Complex operator/(const Complex& rhs) const { if constexpr (std::is_floating_point_v) { return divSmith(rhs); } else { T denom = rhs.re * rhs.re + rhs.im * rhs.im; if (denom == T(0)) { return Complex(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); } return Complex( (re * rhs.re + im * rhs.im) / denom, (im * rhs.re - re * rhs.im) / denom ); } } // ============================================================ // Complex + real / real + complex // ============================================================ [[nodiscard]] constexpr Complex operator+(const T& rhs) const { return Complex(re + rhs, im); } [[nodiscard]] constexpr Complex operator-(const T& rhs) const { return Complex(re - rhs, im); } [[nodiscard]] constexpr Complex operator*(const T& rhs) const { return Complex(re * rhs, im * rhs); } [[nodiscard]] constexpr Complex operator/(const T& rhs) const { return Complex(re / rhs, im / rhs); } friend constexpr Complex operator+(const T& lhs, const Complex& rhs) { return Complex(lhs + rhs.re, rhs.im); } friend constexpr Complex operator-(const T& lhs, const Complex& rhs) { return Complex(lhs - rhs.re, -rhs.im); } friend constexpr Complex operator*(const T& lhs, const Complex& rhs) { return Complex(lhs * rhs.re, lhs * rhs.im); } // Real / complex (Smith's method) friend constexpr Complex operator/(const T& lhs, const Complex& rhs) { if constexpr (std::is_floating_point_v) { return Complex(lhs, T(0)).divSmith(rhs); } else { T denom = rhs.re * rhs.re + rhs.im * rhs.im; if (denom == T(0)) { return Complex(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); } return Complex(lhs * rhs.re / denom, -lhs * rhs.im / denom); } } // ============================================================ // Compound assignment operators // ============================================================ constexpr Complex& operator+=(const Complex& rhs) { re += rhs.re; im += rhs.im; return *this; } constexpr Complex& operator-=(const Complex& rhs) { re -= rhs.re; im -= rhs.im; return *this; } constexpr Complex& operator*=(const Complex& rhs) { *this = *this * rhs; return *this; } constexpr Complex& operator/=(const Complex& rhs) { *this = *this / rhs; return *this; } constexpr Complex& operator+=(const T& rhs) { re += rhs; return *this; } constexpr Complex& operator-=(const T& rhs) { re -= rhs; return *this; } constexpr Complex& operator*=(const T& rhs) { re *= rhs; im *= rhs; return *this; } constexpr Complex& operator/=(const T& rhs) { re /= rhs; im /= rhs; return *this; } // ============================================================ // Member functions // ============================================================ // |z|^2 = re^2 + im^2 [[nodiscard]] constexpr T normSq() const { return re * re + im * im; } // String conversion [[nodiscard]] std::string toString() const { std::ostringstream osRe, osIm; osRe << ((re == T(0)) ? T(0) : re); osIm << ((im == T(0)) ? T(0) : im); std::string sRe = osRe.str(); std::string sIm = osIm.str(); if (sIm == "0" || sIm == "0.") return sRe; std::string s; if (sRe == "0" || sRe == "0.") sRe = ""; if (sIm == "1") s = sRe + "+i"; else if (sIm == "-1") s = sRe + "-i"; else if (!sIm.empty() && sIm[0] == '-') s = sRe + sIm + "i"; else s = sRe + "+" + sIm + "i"; return s; } // Stream output friend std::ostream& operator<<(std::ostream& os, const Complex& z) { return os << z.toString(); } private: // Smith's method: always divide by the larger-magnitude component to avoid // intermediate overflow. Selecting the pivot component (rather than two // symmetric branches that each divide by one component) keeps the divisor // from ever being the smaller component, so a purely-real or purely-imaginary // divisor cannot constant-fold into a division by zero in a not-taken branch // (which would otherwise raise a spurious MSVC C4723). Numerically identical // to the two-branch form, term for term. [[nodiscard]] constexpr Complex divSmith(const Complex& rhs) const { using std::abs; T are = abs(rhs.re); T aim = abs(rhs.im); if (are == T(0) && aim == T(0)) { return Complex(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); } const bool pivotRe = (are >= aim); const T piv = pivotRe ? rhs.re : rhs.im; // larger magnitude, guaranteed nonzero const T oth = pivotRe ? rhs.im : rhs.re; const T r = oth / piv; const T denom = piv + oth * r; const T reN = pivotRe ? (re + im * r) : (re * r + im); const T imN = pivotRe ? (im - re * r) : (im * r - re); return Complex(reN / denom, imN / denom); } }; // ================================================================ // Memory-layout guarantees — SIMD / reinterpret_cast compatible // Complex stores re and im contiguously and shares the same layout as std::complex. // ================================================================ static_assert(sizeof(Complex) == 2 * sizeof(float), "Complex must be tightly packed"); static_assert(sizeof(Complex) == 2 * sizeof(double), "Complex must be tightly packed"); static_assert(offsetof(Complex, re) == 0, "Complex::re must be at offset 0"); static_assert(offsetof(Complex, im) == sizeof(float), "Complex::im must follow re immediately"); static_assert(offsetof(Complex, re) == 0, "Complex::re must be at offset 0"); static_assert(offsetof(Complex, im) == sizeof(double), "Complex::im must follow re immediately"); // ================================================================ // Free functions (math functions) // ================================================================ // --- Basic functions --- template [[nodiscard]] constexpr const T& real(const Complex& z) { return z.re; } template [[nodiscard]] constexpr const T& imag(const Complex& z) { return z.im; } template [[nodiscard]] constexpr Complex conj(const Complex& z) { return Complex(z.re, -z.im); } // |z|^2 (STL-compatible: norm) template [[nodiscard]] constexpr T normSq(const Complex& z) { return z.re * z.re + z.im * z.im; } // |z|^2 (STL-compatible name) template [[nodiscard]] constexpr T norm(const Complex& z) { return z.re * z.re + z.im * z.im; } // |z| (overflow-safe via std::hypot when T = double/float) template [[nodiscard]] T abs(const Complex& z) { if constexpr (std::is_floating_point_v) { return std::hypot(z.re, z.im); } else { using std::sqrt; return sqrt(z.re * z.re + z.im * z.im); } } // arg(z) template [[nodiscard]] T arg(const Complex& z) { using std::atan2; return atan2(z.im, z.re); } // --- Polar form --- template [[nodiscard]] Complex polar(const T& r, const T& theta) { using std::cos; using std::sin; return Complex(r * cos(theta), r * sin(theta)); } // e^(i*omega) template [[nodiscard]] Complex expI(const T& omega) { using std::cos; using std::sin; return Complex(cos(omega), sin(omega)); } // --- Exponential / logarithm --- template [[nodiscard]] Complex exp(const Complex& z) { using std::exp; using std::cos; using std::sin; T r = exp(z.re); return Complex(r * cos(z.im), r * sin(z.im)); } template [[nodiscard]] Complex log(const Complex& z) { using std::log; return Complex(log(abs(z)), arg(z)); } // log10(z) = log(z) * log10(e) where log10(e) = 1/ln(10) template [[nodiscard]] Complex log10(const Complex& z) { if constexpr (std::is_floating_point_v) { static const T inv_ln10 = T(1) / std::log(T(10)); return sangi::log(z) * inv_ln10; } else { // For Float etc.: use the cached constant T::log10e() if available return sangi::log(z) * T::log10e(); } } // --- Exponentiation --- // z^n (integer power) template [[nodiscard]] Complex pow(const Complex& z, int n) { if (n == 0) return Complex(T(1)); if (n < 0) return pow(Complex(T(1)) / z, -n); Complex result(T(1)); Complex base = z; unsigned int m = static_cast(n); while (m > 0) { if (m & 1) result *= base; base *= base; m >>= 1; } return result; } // z^w (complex power) template [[nodiscard]] Complex pow(const Complex& z, const Complex& w) { if (z == T(0)) return Complex(T(0)); return exp(w * log(z)); } // z^a (real power) template [[nodiscard]] Complex pow(const Complex& z, const T& a) { if (z == T(0)) return Complex(T(0)); return exp(a * log(z)); } // --- Square root --- // Source: libg++ 2.7.1 template [[nodiscard]] Complex sqrt(const Complex& z) { using std::sqrt; T r = abs(z); if (r == T(0)) return Complex(T(0), T(0)); T x, y; if (z.re > T(0)) { x = sqrt((r + z.re) / T(2)); y = z.im / (T(2) * x); } else { y = sqrt((r - z.re) / T(2)); if (z.im < T(0)) y = -y; x = z.im / (T(2) * y); } return Complex(x, y); } // --- Trigonometric functions --- template [[nodiscard]] Complex sin(const Complex& z) { // sin(a+bi) = sin(a)cosh(b) + i*cos(a)sinh(b) using std::sin; using std::cos; using std::sinh; using std::cosh; return Complex(sin(z.re) * cosh(z.im), cos(z.re) * sinh(z.im)); } template [[nodiscard]] Complex cos(const Complex& z) { // cos(a+bi) = cos(a)cosh(b) - i*sin(a)sinh(b) using std::sin; using std::cos; using std::sinh; using std::cosh; return Complex(cos(z.re) * cosh(z.im), -sin(z.re) * sinh(z.im)); } template [[nodiscard]] Complex tan(const Complex& z) { return sin(z) / cos(z); } // --- Hyperbolic functions --- template [[nodiscard]] Complex sinh(const Complex& z) { // sinh(z) = -i * sin(i*z) using std::sinh; using std::cosh; using std::sin; using std::cos; return Complex(sinh(z.re) * cos(z.im), cosh(z.re) * sin(z.im)); } template [[nodiscard]] Complex cosh(const Complex& z) { // cosh(z) = cos(i*z) using std::sinh; using std::cosh; using std::sin; using std::cos; return Complex(cosh(z.re) * cos(z.im), sinh(z.re) * sin(z.im)); } template [[nodiscard]] Complex tanh(const Complex& z) { return sinh(z) / cosh(z); } // --- Inverse trigonometric functions --- template [[nodiscard]] Complex asin(const Complex& z) { // asin(z) = -i * log(i*z + sqrt(1 - z^2)) Complex iz(-z.im, z.re); // i*z Complex w = sqrt(Complex(T(1)) - z * z); Complex lv = log(iz + w); return Complex(lv.im, -lv.re); // -i * lv } template [[nodiscard]] Complex acos(const Complex& z) { // acos(z) = -i * log(z + i*sqrt(1 - z^2)) Complex w = sqrt(Complex(T(1)) - z * z); Complex iw(-w.im, w.re); // i*w Complex lv = log(z + iw); return Complex(lv.im, -lv.re); // -i * lv } template [[nodiscard]] Complex atan(const Complex& z) { // atan(z) = (1/2i) * log((1+iz)/(1-iz)) Complex iz(-z.im, z.re); // i*z Complex lv = log((Complex(T(1)) + iz) / (Complex(T(1)) - iz)); // (1/2i) * lv = lv / (2i) = lv * (-i/2) return Complex(lv.im / T(2), -lv.re / T(2)); } // --- Inverse hyperbolic functions --- template [[nodiscard]] Complex asinh(const Complex& z) { return log(z + sqrt(z * z + Complex(T(1)))); } template [[nodiscard]] Complex acosh(const Complex& z) { return log(z + sqrt(z * z - Complex(T(1)))); } template [[nodiscard]] Complex atanh(const Complex& z) { Complex one(T(1)); return log((one + z) / (one - z)) / T(2); } // --- proj (Riemann-sphere projection) --- template [[nodiscard]] Complex proj(const Complex& z) { if constexpr (std::is_floating_point_v) { if (std::isinf(z.re) || std::isinf(z.im)) { return Complex(std::numeric_limits::infinity(), std::copysign(T(0), z.im)); } } return z; } // ================================================================ // User-defined literals // ================================================================ namespace literals { [[nodiscard]] constexpr Complex operator""_i(long double val) { return Complex(0.0, static_cast(val)); } [[nodiscard]] constexpr Complex operator""_i(unsigned long long val) { return Complex(0.0, static_cast(val)); } [[nodiscard]] constexpr Complex operator""_if(long double val) { return Complex(0.0f, static_cast(val)); } [[nodiscard]] constexpr Complex operator""_if(unsigned long long val) { return Complex(0.0f, static_cast(val)); } } // namespace literals // ================================================================ // Type traits // ================================================================ // Traits that test whether a type is a Complex template struct is_complex : std::false_type {}; template struct is_complex> : std::true_type {}; template inline constexpr bool is_complex_v = is_complex::value; // ================================================================ // numeric_traits specialization // ================================================================ // Forward declaration struct complex_tag; template struct numeric_traits; template struct numeric_traits> { using value_type = Complex; using real_type = T; using category = complex_tag; static constexpr bool is_supported = true; static constexpr bool is_complex = true; static constexpr bool is_integer = false; static constexpr bool is_floating_point = false; static Complex zero() { return Complex(T(0), T(0)); } static Complex one() { return Complex(T(1), T(0)); } static T epsilon() { return numeric_traits::epsilon(); } static T abs(const Complex& value) { return sangi::abs(value); } static Complex conj(const Complex& value) { return sangi::conj(value); } static T norm(const Complex& value) { return sangi::normSq(value); } static bool pivotBetter(const Complex& a, const Complex& b) { return abs(a) > abs(b); } static bool isNaN(const Complex& value) { if constexpr (std::is_floating_point_v) { return std::isnan(value.re) || std::isnan(value.im); } else { return false; } } static bool isInfinite(const Complex& value) { if constexpr (std::is_floating_point_v) { return std::isinf(value.re) || std::isinf(value.im); } else { return false; } } static bool isFinite(const Complex& value) { if constexpr (std::is_floating_point_v) { return std::isfinite(value.re) && std::isfinite(value.im); } else { return true; } } static int getSign(const Complex& value) { if (value.re != T(0)) { // Prefer the sign of the real part if constexpr (std::is_arithmetic_v) { return value.re < T(0) ? -1 : 1; } else { return 1; // assume non-arithmetic types are positive } } if (value.im != T(0)) { if constexpr (std::is_arithmetic_v) { return value.im < T(0) ? -1 : 1; } else { return 1; } } return 0; } }; } // namespace sangi // Add Complex abs/sqrt overloads to the std namespace (for ADL fallback) namespace std { template auto abs(const sangi::Complex& z) { return sangi::abs(z); } template sangi::Complex sqrt(const sangi::Complex& z) { return sangi::sqrt(z); } }