Complex — Complex Numbers

Overview

Complex<T> is a complex number class parameterized over an arbitrary scalar type T. T may be double, float, Int, Float, Rational, or any other type that satisfies the ComplexScalar concept.

  • Arbitrary precision support — unlike std::complex, the implementation accepts arbitrary-precision numeric types as the template parameter.
  • Strassen 3-multiplication — multiplication uses three real multiplications instead of four, which is a significant speedup for arbitrary-precision arithmetic.
  • Smith division — when T = double/float, Smith's method is used to avoid overflow in intermediate values.
  • std::complex compatibility — conversion constructors and implicit conversion operators allow bidirectional conversion.
  • SIMD-compatible layoutstatic_assert guarantees that re and im are laid out contiguously, so reinterpret_cast to std::complex<T> is valid.

The ComplexScalar concept requires the following operations:

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) };
};

Build

#include <math/core/Complex.hpp>

Header-only. No CMake target or link library is required. Complex.hpp depends only on standard headers such as <complex>, <cmath>, and <concepts>.

Constructors

SignatureDescription
Complex() Default constructor. Initializes both real and imaginary parts to T(0).
Complex(const T& real) Constructs from a real value. The imaginary part is T(0). Implicit conversion is allowed.
Complex(const T& real, const T& imag) Constructs from explicit real and imaginary parts.
explicit Complex(const Complex<U>& other) Conversion from a different scalar type U. Requires an explicit cast.
Complex(const std::complex<U>& sc) Conversion from std::complex. Available only when U is a floating-point type convertible to T.

Accessors

Member / MethodDescription
T re Public member variable holding the real part.
T im Public member variable holding the imaginary part.
const T& real() const Returns a const reference to the real part.
T& real() Returns a mutable reference to the real part.
void real(const T& val) Sets the real part to val.
const T& imag() const Returns a const reference to the imaginary part.
T& imag() Returns a mutable reference to the imaginary part.
void imag(const T& val) Sets the imaginary part to val.
T normSq() const Returns $|z|^2 = \mathrm{re}^2 + \mathrm{im}^2$.
std::string toString() const Returns a string representation (e.g. "3+4i", "-2i").

The re and im members are public and may be accessed directly. The .real() / .imag() methods are provided for API compatibility with std::complex.

Arithmetic Operators

Complex-Complex operations

OperatorDescription
z + w Complex addition (component-wise).
z - w Complex subtraction (component-wise).
z * w Complex multiplication. When T is a floating-point type (double/float), the standard 4-multiplication + 2-add/sub formula is used. For arbitrary-precision types (Int, Float, etc.), Strassen's 3-multiplication (3 multiplications + 5 add/subs) is used, which is faster when multiplication cost dominates.
z / w Complex division. When T is a floating-point type, Smith's method is used to avoid overflow in intermediate values. For arbitrary-precision types, the naive formula ($\mathrm{denom} = c^2 + d^2$) is used. Division by zero returns NaN.
+z, -z Unary plus (identity) and unary minus (negation).

Scalar-mixed operations

Arithmetic between Complex<T> and T is supported with the scalar on either side.

OperatorDescription
z + s, s + zAdds the scalar to the real part.
z - s, s - zSubtracts the scalar from the real part / subtracts the complex from the scalar.
z * s, s * zMultiplies both real and imaginary parts by the scalar.
z / sDivides both real and imaginary parts by the scalar.
s / zDivides the scalar by the complex. Uses Smith's method for floating-point types.

Compound assignment operators

+=, -=, *=, /= are supported for both Complex-Complex and Complex-scalar combinations.

Comparison Operators

OperatorDescription
z == w Returns true when both the real and imaginary parts are equal. Note that for floating-point types this is an exact comparison.
z != w Negation of ==.
z == s, s == z Returns true when the real part equals the scalar and the imaginary part is zero.
z != s, s != z Negation of the above.

Ordering comparisons (<, >, etc.) are not provided because the complex numbers do not admit a natural mathematical ordering.

Mathematical Functions

All functions are free functions in the sangi namespace. They can be called without the namespace prefix thanks to ADL (Argument-Dependent Lookup).

Basic functions

FunctionSignatureDescription
abs(z) T abs(const Complex<T>&) Returns the magnitude $|z| = \sqrt{a^2 + b^2}$. For floating-point T, std::hypot is used to avoid overflow.
arg(z) T arg(const Complex<T>&) Returns the argument $\mathrm{arg}(z) = \mathrm{atan2}(b, a)$. The return value is in the range $(-\pi, \pi]$.
conj(z) Complex<T> conj(const Complex<T>&) Returns the complex conjugate $\bar{z} = a - bi$.
norm(z) T norm(const Complex<T>&) Returns the squared magnitude $|z|^2 = a^2 + b^2$. Named for STL compatibility (matches std::norm).
normSq(z) T normSq(const Complex<T>&) Same as norm(z) but with a more descriptive name.
real(z) const T& real(const Complex<T>&) Returns the real part.
imag(z) const T& imag(const Complex<T>&) Returns the imaginary part.
proj(z) Complex<T> proj(const Complex<T>&) Returns the projection onto the Riemann sphere. When either part is infinite, the value is mapped to $(\infty, \pm 0)$. Finite values are returned unchanged.

Polar form

FunctionSignatureDescription
polar(r, theta) Complex<T> polar(const T&, const T&) Constructs from polar form $r e^{i\theta} = r\cos\theta + ir\sin\theta$.
expI(omega) Complex<T> expI(const T&) Returns $e^{i\omega} = \cos\omega + i\sin\omega$. Useful for FFT twiddle factors and similar.

Exponential, logarithm, and power

FunctionSignatureDescription
exp(z) Complex<T> exp(const Complex<T>&) Returns $e^z = e^a(\cos b + i\sin b)$.
log(z) Complex<T> log(const Complex<T>&) Returns the natural logarithm $\ln z = \ln|z| + i\,\mathrm{arg}(z)$. The principal value (imaginary part in $(-\pi, \pi]$) is returned.
log10(z) Complex<T> log10(const Complex<T>&) Returns the common (base-10) logarithm $\log_{10} z = \ln z \cdot \log_{10} e$. For arbitrary-precision types, the cached constant T::log10e() is reused.
sqrt(z) Complex<T> sqrt(const Complex<T>&) Returns the principal square root (the branch with non-negative real part).
pow(z, n) Complex<T> pow(const Complex<T>&, int) Integer power $z^n$ computed via binary exponentiation. For negative $n$, the reciprocal is taken first.
pow(z, w) Complex<T> pow(const Complex<T>&, const Complex<T>&) Complex power $z^w = e^{w \ln z}$. Returns $0$ when $z = 0$.
pow(z, a) Complex<T> pow(const Complex<T>&, const T&) Real power $z^a = e^{a \ln z}$.

Trigonometric functions

FunctionDefinition
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$

Hyperbolic functions

FunctionDefinition
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$

Inverse trigonometric functions

FunctionDefinition
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}$

Inverse hyperbolic functions

FunctionDefinition
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}$

User-Defined Literals

Declaring using sangi::literals; enables the following literals.

LiteralTypeExample
_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)

Compatibility with std::complex

Conversion

When T is a floating-point type (float, double, long double), the following conversions are implicit:

// std::complex → Complex
std::complex<double> sc(1.0, 2.0);
Complex<double> z = sc;     // implicit

// Complex → std::complex
Complex<double> w(3.0, 4.0);
std::complex<double> sw = w; // implicit

Memory layout compatibility

Complex<T> guarantees via static_assert that re and im are laid out contiguously in memory. Therefore, an array of Complex<double> can be reinterpreted as an array of std::complex<double> via reinterpret_cast.

// Array reinterpretation
std::vector<Complex<double>> data(1024);
auto* std_ptr = reinterpret_cast<std::complex<double>*>(data.data());
// Can be passed directly to C libraries such as FFTW

The following static_asserts verify the layout at compile time:

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

auto and Expression Templates

Complex<T> itself does not use expression templates. All operators return Complex<T> directly, so receiving the result with auto is always safe.

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

However, when Complex<T> is used as the element type of a container that does use expression templates (such as Matrix<Complex<T>>), receiving the result of a matrix operation with auto may yield an expression-template proxy. In that case, call .eval() or specify the type explicitly.

Matrix<Complex<double>> A, B;
auto expr = A * B;                           // may be a proxy type
Matrix<Complex<double>> C = A * B;          // forces evaluation
auto D = (A * B).eval();                     // explicit evaluation via eval()

Code Example

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

int main() {
    // Basic construction
    Complex<double> z1(3.0, 4.0);   // 3 + 4i
    Complex<double> z2 = 2.0 + 1.0_i; // 2 + i

    // Arithmetic
    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;   // computed via Smith's method

    // Mathematical functions
    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

    // Polar form
    auto w = polar(5.0, 0.9273);  // ≈ 3 + 4i

    // Exponential and logarithm
    auto e_iz = exp(Complex<double>(0.0, M_PI));  // ≈ -1 + 0i (Euler's formula)
    std::cout << "e^(i*pi) = " << e_iz << std::endl;

    // Conversion to/from std::complex
    std::complex<double> sc(1.0, 2.0);
    Complex<double> from_std = sc;
    std::complex<double> to_std = from_std;

    // expI: FFT twiddle factor
    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;
    }
}