Complex Demo — Complex Number Arithmetic

The sangi Complex<T> class handles complex numbers uniformly from double all the way up to the arbitrary-precision Float type. This page walks through three demos that illustrate representative use cases.

Demo 1: Basic complex arithmetic

Compute the four arithmetic operations along with absolute value (abs), argument (arg), and conjugate (conj) for Complex<double>. Declaring using sangi::literals enables user-defined literals such as 3.0_i for intuitive imaginary-number notation.

=== Complex<double> Basic Operations === a = (2+3i) b = (1-1i) a + b = (3+2i) a - b = (1+4i) a * b = (5+1i) a / b = (-0.5+2.5i) abs(a) = 3.60555 arg(a) = 0.982794 (rad) conj(a) = (2-3i)
Demo 1 source code
// demo1_basic.cpp — Complex<double> basic arithmetic
#include <math/core/Complex.hpp>
#include <iostream>
#include <iomanip>
using namespace sangi;
using namespace sangi::literals;  // enables 3.0_i literals

int main() {
    std::cout << std::setprecision(6);
    std::cout << "=== Complex<double> Basic Operations ===\n";

    // Build complex numbers with user-defined literals
    auto a = 2.0 + 3.0_i;   // (2, 3)
    auto b = 1.0 - 1.0_i;   // (1, -1)

    std::cout << "a = " << a << "\n";
    std::cout << "b = " << b << "\n\n";

    // Four arithmetic operations
    std::cout << "a + b = " << (a + b) << "\n";
    std::cout << "a - b = " << (a - b) << "\n";
    std::cout << "a * b = " << (a * b) << "\n";
    std::cout << "a / b = " << (a / b) << "\n\n";

    // Absolute value, argument, conjugate
    std::cout << "abs(a) = " << abs(a) << "\n";
    std::cout << "arg(a) = " << arg(a) << "  (rad)\n";
    std::cout << "conj(a) = " << conj(a) << "\n";

    return 0;
}
With the sangi::literals namespace, 3.0_i is converted to Complex<double>(0, 3.0). Combined with standard arithmetic operators, this lets you write code that closely matches the mathematical notation.

Demo 2: Mandelbrot set membership

Determine whether a complex number $c$ belongs to the Mandelbrot set by iterating the recurrence $z_{n+1} = z_n^2 + c$ with $z_0 = 0$. If $|z| > 2$ has not been reached after 100 iterations, treat $c$ as a member of the set. We classify several lattice points and print the result.

=== Mandelbrot Set Membership === c = (0+0i) : IN (|z| = 0.000000, iter = 100) c = (0.25+0i) : IN (|z| = 0.500000, iter = 100) c = (-1+0i) : IN (|z| = 0.000000, iter = 100) c = (1+0i) : OUT (|z| = 2.396970, iter = 3) c = (-0.5+0.5i) : IN (|z| = 0.627930, iter = 100) c = (0.3+0.5i) : OUT (|z| = 2.301270, iter = 10) c = (-1.5+0i) : OUT (|z| = 2.261458, iter = 24) c = (-0.75+0.1i) : IN (|z| = 1.324718, iter = 100)
Demo 2 source code
// demo2_mandelbrot.cpp — Mandelbrot set membership
#include <math/core/Complex.hpp>
#include <iostream>
#include <iomanip>
#include <vector>
using namespace sangi;

bool isMandelbrot(Complex<double> c, int maxIter, int& iterOut,
                  double& absOut) {
    Complex<double> z(0.0, 0.0);
    for (int i = 0; i < maxIter; ++i) {
        z = z * z + c;
        if (abs(z) > 2.0) {
            iterOut = i + 1;
            absOut = abs(z);
            return false;  // diverges → not in the set
        }
    }
    iterOut = maxIter;
    absOut = abs(z);
    return true;  // bounded → in the set
}

int main() {
    std::cout << std::setprecision(6) << std::fixed;
    std::cout << "=== Mandelbrot Set Membership ===\n";

    std::vector<Complex<double>> points = {
        {0.0, 0.0}, {0.25, 0.0}, {-1.0, 0.0}, {1.0, 0.0},
        {-0.5, 0.5}, {0.3, 0.5}, {-1.5, 0.0}, {-0.75, 0.1}
    };

    constexpr int maxIter = 100;
    for (auto& c : points) {
        int iter;
        double absZ;
        bool inSet = isMandelbrot(c, maxIter, iter, absZ);
        std::cout << "c = " << std::setw(16) << std::left << c
                  << ": " << (inSet ? "IN " : "OUT")
                  << "  (|z| = " << absZ
                  << ", iter = " << iter << ")\n";
    }

    return 0;
}
The Mandelbrot set is the set of $c$ for which $z_{n+1} = z_n^2 + c$ does not diverge. Once $|z| > 2$, divergence is guaranteed, so the iteration can be terminated early at the threshold of 2. The origin $(0, 0)$ and $(-1, 0)$ belong to the set, while $(1, 0)$ diverges in only three iterations.

Demo 3: Complex mathematical functions

Verify Euler's formula $e^{i\pi} = -1$ with Complex<double>, then evaluate the complex versions of sin, cos, exp, log, and sqrt. Finally, use the arbitrary-precision Complex<Float> to verify $e^{i\pi} + 1 = 0$ to 100 digits.

=== Euler's Formula: exp(i*pi) === exp(i*pi) = (-1+1.22465e-16i) exp(i*pi) + 1 = (0+1.22465e-16i) (imag error is double rounding noise) === Complex Math Functions === z = (1+1i) sin(z) = (1.29846+0.634964i) cos(z) = (0.83373-0.988898i) exp(z) = (1.46869+2.28736i) log(z) = (0.346574+0.785398i) sqrt(z) = (1.09868+0.45509i) === High-Precision: Complex<Float> (100 digits) === exp(i*pi) + 1 = real: 0.0000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000 imag: 0.0000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000
Demo 3 source code
// demo3_math_functions.cpp — complex math functions and high-precision computation
#include <math/core/Complex.hpp>
#include <math/core/Float.hpp>
#include <iostream>
#include <iomanip>
#include <numbers>
using namespace sangi;
using namespace sangi::literals;

int main() {
    // --- Euler's formula ---
    std::cout << "=== Euler's Formula: exp(i*pi) ===\n";
    auto ipi = Complex<double>(0.0, std::numbers::pi);
    auto euler = exp(ipi);
    std::cout << "exp(i*pi) = " << euler << "\n";
    std::cout << "exp(i*pi) + 1 = " << (euler + 1.0) << "\n";
    std::cout << "  (the imaginary error is double rounding noise)\n";

    // --- Complex math functions ---
    std::cout << "\n=== Complex Math Functions ===\n";
    auto z = 1.0 + 1.0_i;
    std::cout << std::setprecision(6);
    std::cout << "z = " << z << "\n";
    std::cout << "sin(z)  = " << sin(z) << "\n";
    std::cout << "cos(z)  = " << cos(z) << "\n";
    std::cout << "exp(z)  = " << exp(z) << "\n";
    std::cout << "log(z)  = " << log(z) << "\n";
    std::cout << "sqrt(z) = " << sqrt(z) << "\n";

    // --- Arbitrary-precision Complex<Float> ---
    std::cout << "\n=== High-Precision: Complex<Float> (100 digits) ===\n";
    constexpr int digits = 100;
    Float::setDefaultPrecision(digits);

    Float pi = Float::pi(digits);
    Complex<Float> ipi_hp(Float(0), pi);
    Complex<Float> result = exp(ipi_hp) + Complex<Float>(Float(1));

    std::cout << "exp(i*pi) + 1 =\n";
    std::cout << "  real: " << result.real().toString(digits) << "\n";
    std::cout << "  imag: " << result.imag().toString(digits) << "\n";

    return 0;
}
With Complex<double>, Euler's formula leaves a rounding error of $\approx 10^{-16}$. Switching to Complex<Float> verifies $e^{i\pi} + 1 = 0$ at arbitrary precision. sangi's Complex<T> lets you change precision purely by swapping the template parameter — the API stays exactly the same.

Source code and how to build

example_complex.cpp (full source)
// example_complex.cpp
// Complex<T> demo: basic operations, Mandelbrot membership, math functions,
// high-precision Complex<Float>

#include <math/core/Complex.hpp>
#include <math/core/mp/Float.hpp>
#include <iostream>
#include <iomanip>
#include <vector>
#include <numbers>

using namespace sangi;
using namespace sangi::literals;

// --- Demo 1: Basic arithmetic ---
void demo1_basic() {
    std::cout << std::setprecision(6);
    std::cout << "=== Complex<double> Basic Operations ===\n";

    auto a = 2.0 + 3.0_i;
    auto b = 1.0 - 1.0_i;

    std::cout << "a = " << a << "\n";
    std::cout << "b = " << b << "\n\n";

    std::cout << "a + b = " << (a + b) << "\n";
    std::cout << "a - b = " << (a - b) << "\n";
    std::cout << "a * b = " << (a * b) << "\n";
    std::cout << "a / b = " << (a / b) << "\n\n";

    std::cout << "abs(a) = " << abs(a) << "\n";
    std::cout << "arg(a) = " << arg(a) << "  (rad)\n";
    std::cout << "conj(a) = " << conj(a) << "\n";
}

// --- Demo 2: Mandelbrot set membership ---
bool isMandelbrot(Complex<double> c, int maxIter, int& iterOut,
                  double& absOut) {
    Complex<double> z(0.0, 0.0);
    for (int i = 0; i < maxIter; ++i) {
        z = z * z + c;
        if (abs(z) > 2.0) {
            iterOut = i + 1;
            absOut = abs(z);
            return false;
        }
    }
    iterOut = maxIter;
    absOut = abs(z);
    return true;
}

void demo2_mandelbrot() {
    std::cout << "\n=== Mandelbrot Set Membership ===\n";
    std::cout << std::setprecision(6) << std::fixed;

    std::vector<Complex<double>> points = {
        {0.0, 0.0}, {0.25, 0.0}, {-1.0, 0.0}, {1.0, 0.0},
        {-0.5, 0.5}, {0.3, 0.5}, {-1.5, 0.0}, {-0.75, 0.1}
    };

    constexpr int maxIter = 100;
    for (auto& c : points) {
        int iter;
        double absZ;
        bool inSet = isMandelbrot(c, maxIter, iter, absZ);
        std::cout << "c = " << std::setw(16) << std::left << c
                  << ": " << (inSet ? "IN " : "OUT")
                  << "  (|z| = " << absZ
                  << ", iter = " << iter << ")\n";
    }
}

// --- Demo 3: Math functions and high-precision computation ---
void demo3_math() {
    std::cout << "\n=== Euler's Formula: exp(i*pi) ===\n";
    std::cout << std::setprecision(6) << std::defaultfloat;
    auto ipi = Complex<double>(0.0, std::numbers::pi);
    auto euler = exp(ipi);
    std::cout << "exp(i*pi) = " << euler << "\n";
    std::cout << "exp(i*pi) + 1 = " << (euler + 1.0) << "\n";
    std::cout << "  (imag error is double rounding noise)\n";

    std::cout << "\n=== Complex Math Functions ===\n";
    auto z = 1.0 + 1.0_i;
    std::cout << "z = " << z << "\n";
    std::cout << "sin(z)  = " << sin(z) << "\n";
    std::cout << "cos(z)  = " << cos(z) << "\n";
    std::cout << "exp(z)  = " << exp(z) << "\n";
    std::cout << "log(z)  = " << log(z) << "\n";
    std::cout << "sqrt(z) = " << sqrt(z) << "\n";

    std::cout << "\n=== High-Precision: Complex<Float> (100 digits) ===\n";
    constexpr int digits = 100;
    Float::setDefaultPrecision(digits);

    Float pi = Float::pi(digits);
    Complex<Float> ipi_hp(Float(0), pi);
    Complex<Float> result = exp(ipi_hp) + Complex<Float>(Float(1));

    std::cout << "exp(i*pi) + 1 =\n";
    std::cout << "  real: " << result.real().toString(digits) << "\n";
    std::cout << "  imag: " << result.imag().toString(digits) << "\n";
}

int main() {
    demo1_basic();
    demo2_mandelbrot();
    demo3_math();
    return 0;
}

For full API details, see the Complex API reference.

Build and run

cd sangi
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release --target example-complex
examples\Release\example-complex.exe