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 layout —
static_assertguarantees thatreandimare laid out contiguously, soreinterpret_casttostd::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
| Signature | Description |
|---|---|
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 / Method | Description |
|---|---|
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
| Operator | Description |
|---|---|
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.
| Operator | Description |
|---|---|
z + s, s + z | Adds the scalar to the real part. |
z - s, s - z | Subtracts the scalar from the real part / subtracts the complex from the scalar. |
z * s, s * z | Multiplies both real and imaginary parts by the scalar. |
z / s | Divides both real and imaginary parts by the scalar. |
s / z | Divides 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
| Operator | Description |
|---|---|
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
| Function | Signature | Description |
|---|---|---|
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
| Function | Signature | Description |
|---|---|---|
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
| Function | Signature | Description |
|---|---|---|
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
| Function | Definition |
|---|---|
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
| Function | Definition |
|---|---|
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
| Function | Definition |
|---|---|
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
| Function | Definition |
|---|---|
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.
| Literal | Type | Example |
|---|---|---|
_i |
Complex<double> |
3.0_i → $0 + 3i$, 3_i → $0 + 3i$ |
_if |
Complex<float> |
3.0_if → Complex<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) == 0offsetof(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;
}
}