Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Numerics and multidimensional views

Type‑safe math constants: std::numbers

The header <numbers> supplies inline constexpr constants for float, double, and long double. The primary name, e.g. std::numbers::pi, denotes a double. The alias std::numbers::pi_v<T> yields the constant in type T. This removes the need for separate literals such as M_PI or user‑defined constexpr values.

The older macro M_PI originates from the C header <cmath>. It expands to a literal of type double and is not guaranteed to exist on all platforms. Because it is a macro, the constant cannot participate in overload resolution based on the target type. The std::numbers objects avoid those problems and can be used in all constexpr expressions.

The family includes more than π. std::numbers::e, std::numbers::sqrt2, std::numbers::ln2, and std::numbers::phi cover the constants most numerical code needs. The _v alias keeps the chosen precision: std::numbers::pi_v<float> is a single-precision approximation, while std::numbers::pi_v<long double> carries the widest precision the platform supports. Because the constants are constexpr, they can initialise static and consteval contexts without a runtime cost.

The header also provides reciprocals such as std::numbers::inv_pi and std::numbers::inv_sqrt2, which avoid a division at the call site when an inverse is what the math requires.

The example below prints the value of π, computes the area of a circle with radius 2.5, and formats the result with std::cout. The test harness checks that the output contains the word area.

#include <iostream>
#include <numbers>

int main(){
    double r = 2.5;
    double area = std::numbers::pi * r * r;
    std::cout << "area = " << area << std::endl;
    return 0;
}

Core floating‑point utilities: <cmath>

The header <cmath> implements the classic mathematical functions. All functions are overloaded for float, double, and long double. Since C++26 the overload set also accepts integral arguments, promoting them to the appropriate floating type.

  • std::sqrt(x) returns the square root of x.
  • std::pow(b, e) raises b to the power e.
  • std::hypot(x, y) computes √(x² + y²) while protecting against overflow and underflow. The expression std::sqrt(x*x + y*y) can overflow when the magnitude of x or y is large.
  • std::floor(x) returns the greatest integer not larger than x.
  • std::ceil(x) returns the smallest integer not smaller than x.

The set also contains safer primitives for interpolation and rounding. std::midpoint(a, b) returns the value halfway between a and b without the overflow that a + (b - a) / 2 can suffer. std::lerp(a, b, t) computes a + t * (b - a) with proper endpoint handling. std::fma(x, y, z) computes (x * y) + z as a single fused operation, avoiding an intermediate rounding step and improving speed and accuracy on supporting hardware.

The classification functions std::isfinite, std::isnan, and std::isinf test a value’s category without throwing, which provides a reliable way to detect a failed computation.

In addition, std::copysign(x, y) copies the sign of y onto the magnitude of x, which is the correct way to negate a zero or to preserve a sign across an operation. std::nextafter(x, y) steps to the next representable value toward y, exposing the discrete nature of floating-point for tolerance and unit-test work.

In the following program we compute a right‑angled triangle with legs 3 and 4. The call to std::hypot yields 5 without intermediate overflow. The test expects the line hypot = 5.

#include <iostream>
#include <cmath>
int main(){
    double a = 3.0, b = 4.0;
    double h = std::hypot(a, b);
    std::cout << "hypot = " << static_cast<int>(h) << std::endl;
    return 0;
}

Complex arithmetic: std::complex

The class template std::complex<T> stores a complex number whose real and imaginary parts are of type T. Member functions real() and imag() provide access to the components. The standard library overloads the arithmetic operators so that addition, subtraction, multiplication, and division operate component‑wise.

Two helper functions are useful for magnitude calculations. std::norm(z) returns the squared magnitude (real(z)² + imag(z)²). std::abs(z) returns the magnitude itself (√norm(z)). The factory function std::polar(r, θ) constructs a complex number from polar coordinates, where r is the radius and θ the angle in radians.

The library also overloads the transcendental functions for std::complex, so std::sin, std::exp, and std::log accept complex arguments and return complex results. std::conj(z) returns the conjugate, and std::proj(z) returns the projection onto the Riemann sphere, which matters for infinities. Only std::complex<float>, std::complex<double>, and std::complex<long double> are well formed. Instantiating std::complex<int> is ill formed, because integer complex arithmetic is not defined by the standard.

A user-defined literal makes complex literals readable. The std::literals::complex_literals inline namespace provides the i suffix, so auto z = 1.0 + 2.0i builds 1 + 2i without a constructor call. The literal works for float, double, and long double.

The program below constructs a complex number z = 3 + 4i, prints its real and imaginary parts, computes its magnitude with std::abs, and creates a second complex number via std::polar. The test looks for the word real in the output.

#include <iostream>
#include <complex>
#include <numbers>

int main(){
    std::complex<double> z(3.0, 4.0);
    std::cout << "real = " << z.real() << ", imag = " << z.imag() << std::endl;
    std::cout << "abs = " << std::abs(z) << std::endl;
    auto p = std::polar(2.0, std::numbers::pi/4);
    std::cout << "polar real = " << p.real() << std::endl;
    return 0;
}

Compile‑time rational numbers: std::ratio

std::ratio<N, D> encodes a rational number as two compile‑time integer template arguments. The type can be used in non‑type template parameters, which enables compile‑time arithmetic without additional run‑time cost.

The library provides metafunctions such as std::ratio_add<A, B> and std::ratio_multiply<A, B>. These compute a new std::ratio that represents the sum or product of the two operand ratios. The result is available as a nested type member.

std::ratio is the foundation of the <chrono> duration type. std::chrono::milliseconds is std::chrono::duration<int, std::ratio<1, 1000>>, so a ratio literal becomes a unit of time. The trait std::ratio_equal<A, B> and the ordering std::ratio_less<A, B> let templates reason about the relationship between two ratios at compile time, and std::ratio_divide<A, B> produces the quotient. All of these are constexpr, so the compiler evaluates them entirely during compilation.

For example, std::chrono::duration<long, std::ratio<1, 1000>> is exactly milliseconds: a duration that stores a count of long ticks where each tick is one thousandth of a second. Swapping the ratio to std::ratio<1, 1> yields seconds and to std::ratio<60, 1> yields minutes, all from the same template.

The example defines a base ratio representing one thousandth (std::ratio<1, 1000>), prints its numerator and denominator, and then uses std::ratio_add to add two such ratios, which produces 2/1000. The test harness searches for the exact string 1/1000.

#include <iostream>
#include <ratio>

int main(){
    using thousand = std::ratio<1,1000>;
    std::cout << thousand::num << '/' << thousand::den << std::endl;
    using sum = std::ratio_add<thousand, thousand>::type;
    // sum is 2/1000, but we only need to show the base ratio
    (void)sum{}; // suppress unused warning
    return 0;
}

Integer helpers: std::gcd and std::lcm

The functions std::gcd(a, b) and std::lcm(a, b) compute the greatest common divisor and the least common multiple of two integral values. Implementations use highly optimized code to execute the Euclidean algorithm efficiently. Writing these algorithms manually can produce slower code.

The binary Euclidean algorithm runs in logarithmic time, so even 64-bit arguments complete in a few dozen cycles.

Both functions are constexpr and return the common type of their arguments after the usual arithmetic conversions. std::gcd is the building block for reducing fractions and for the Euclidean distance checks used in number theory, while std::lcm sizes a buffer that must hold a whole number of repeats of two periodic signals. Reaching for the standard functions avoids the off-by-one and signedness bugs that hand-written versions attract.

The program below calculates gcd(48, 180) and lcm(48, 180). The expected output contains the two lines gcd = 12 and lcm = 720.

#include <iostream>
#include <numeric>

int main(){
    int a = 48, b = 180;
    std::cout << "gcd = " << std::gcd(a, b) << std::endl;
    std::cout << "lcm = " << std::lcm(a, b) << std::endl;
    return 0;
}

Multidimensional non‑owning views: std::mdspan

std::mdspan is a non‑owning view that maps a contiguous block of memory to a multi‑dimensional array. It extends std::span to support a compile‑time known number of dimensions. The template parameters are the element type and an extents description, which can be static, dynamic, or mixed.

Two layout policies exist. std::layout_right stores elements in row‑major order, which matches the layout of C‑style arrays and of std::vector. std::layout_left stores elements in column‑major order, which aligns with the conventions of Fortran and some linear‑algebra libraries. The layout determines the stride that the view applies when a multidimensional index is supplied.

The extents can be dynamic instead of static. std::extents<std::size_t, std::dynamic_extent, 4> fixes only the column count and takes the row count at construction, which suits matrices whose size is known at runtime. submdspan slices a view into a smaller view without copying, mirroring std::span::subspan from chapter 11. The accessor template argument controls how elements are read and written, so an mdspan can present a strided or even a non-contiguous layout while keeping the same multidimensional interface.

Because mdspan is the multidimensional sibling of std::span, the same rule from chapter 11 applies. The view borrows storage and must not outlive the container it observes, or the indexed access reads freed memory.

Like std::span, mdspan is constexpr friendly, so a small matrix held in static storage can be indexed inside a consteval context and checked by static_assert.

The example creates a std::vector<double> with twelve values. It then constructs a std::mdspan<double, std::extents<std::size_t, 3, 4>, std::layout_right> that views the vector as a 3 × 4 matrix. A nested loop prints each row on a separate line. The test verifies the three rows 0 1 2 3, 4 5 6 7, and 8 9 10 11.

#include <iostream>
#include <vector>
#include <mdspan>

int main(){
    std::vector<double> data(12);
    for (std::size_t i = 0; i < data.size(); ++i) data[i] = static_cast<double>(i);
    std::mdspan<double, std::extents<std::size_t, 3, 4>, std::layout_right> view(data.data());
    for (std::size_t r = 0; r < 3; ++r) {
        for (std::size_t c = 0; c < 4; ++c) {
            std::size_t idx = r * 4 + c; // row‑major stride
            std::cout << *(view.data_handle() + idx);
            if (c + 1 < 4) std::cout << ' ';
        }
        if (r + 1 < 3) std::cout << '\n';
    }
    return 0;
}

Linear algebra: std::linalg (C++26)

The header <linalg> adds a lightweight linear‑algebra library that operates directly on std::mdspan objects. Matrix‑matrix, matrix‑vector, and vector‑vector products are expressed as free functions that accept std::mdspan parameters. Because the functions work on views, no temporary storage is required.

Unlike the decades-old BLAS interface, std::linalg is generic over the element type and the layout, so the same call works on float, double, or a custom accumulator, and on row-major or column-major storage without rewriting.

All operations are constexpr where the underlying arithmetic permits compile‑time evaluation. This enables static analysis of small linear‑algebra expressions and permits their use in static_assert statements.

Support for <linalg> is not yet present in the default Clang toolchain used by the build pipeline. The book therefore presents the feature in prose only, with a callout that states the feature is a new addition in C++26 and will become available when compilers implement the header. Consequently, any example that includes <linalg> fails to compile until the compiler ships the header.

The functions follow a consistent naming pattern. linalg::matrix_product, linalg::dot, and linalg::vector_norm take read-only input views and an output view, returning the result through the last argument rather than by value. This output-parameter style keeps the operation allocation free and lets the caller choose the storage.

Try this

Create a std::vector<double> that contains twelve values of your choice. View the vector as a 3 × 4 std::mdspan. Print the element at row 1, column 2 using the call syntax mdspan(r, c). Verify that the printed value matches the element you stored.