Numbers and Math

This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com.

This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Floating-Point Pitfalls

IEEE-754 floating point cannot represent most decimal fractions exactly, so direct equality comparison is almost always wrong:

#include <cmath>
#include <cassert>

int main() {
    double a = 0.1 + 0.2;
    assert(a != 0.3);                              // true! 0.1+0.2 is 0.30000000000000004...
    assert(std::abs(a - 0.3) < 1e-9);               // compare within a tolerance instead
    return 0;
}

Other pitfalls: NaN compares unequal to everything, including itself (std::isnan(x) is the only reliable test); accumulating many small floating-point additions loses precision (prefer std::accumulate with a wider accumulator type, or a compensated-summation algorithm like Kahan summation, for long reductions).

<cmath>

#include <cmath>
#include <cassert>

int main() {
    assert(std::sqrt(16.0) == 4.0);
    assert(std::pow(2.0, 10.0) == 1024.0);
    assert(std::abs(-5) == 5);
    assert(std::floor(3.7) == 3.0);
    assert(std::ceil(3.2) == 4.0);
    assert(std::round(3.5) == 4.0);
    assert(std::isnan(std::sqrt(-1.0)));
    assert(!std::isfinite(1.0 / 0.0));    // infinity
    return 0;
}

<numbers>

C++20’s <numbers> replaces hand-copied constants like #define M_PI with type-generic, constexpr values:

#include <numbers>
#include <iostream>

int main() {
    std::cout << std::numbers::pi << '\n';         // double, by default
    std::cout << std::numbers::pi_v<float> << '\n'; // explicit precision
    std::cout << std::numbers::e << '\n';
    std::cout << std::numbers::sqrt2 << '\n';
}

<random>: Engines, Distributions, and Seeding

<random> separates the engine (a source of uniformly distributed bits) from the distribution (how those bits are shaped into a useful range) — never use rand()/srand() in new code, they are low-quality and their period/distribution are unspecified:

#include <random>
#include <iostream>

int main() {
    std::random_device rd;                    // a (usually hardware) source of non-deterministic entropy
    std::mt19937 engine(rd());                 // Mersenne Twister, seeded from rd() -- fast, good statistical
                                                // quality, NOT cryptographically secure
    std::uniform_int_distribution<int> dice(1, 6);
    std::cout << dice(engine) << '\n';          // a uniformly distributed integer in [1, 6]

    std::mt19937 reproducible(42);              // a fixed seed -- makes the sequence reproducible for tests
    std::normal_distribution<double> heights(170.0, 10.0);   // mean, stddev
    std::cout << heights(reproducible) << '\n';
}

Reseeding the same mt19937 with a fixed integer literal, as reproducible does above, is exactly how to make a randomized test deterministic without giving up the same distribution shape used in production.

<bit> and std::bitset

<bit>’s free functions (covered in Operators and Expressions) operate on plain unsigned integers; `std::bitset<N> is a fixed-size, indexable, printable bit sequence:

#include <bitset>
#include <iostream>

int main() {
    std::bitset<8> flags(0b0010'1100);
    flags.set(0);              // turn bit 0 on
    flags.flip(1);              // toggle bit 1
    std::cout << flags << '\n';           // prints as "00101111"
    std::cout << flags.count() << '\n';    // number of set bits -- 5
    std::cout << flags.to_ulong() << '\n'; // as an unsigned long -- 47
}

Checked Conversions

std::in_range<T>(value) (C++20, <utility>) checks whether a value fits in a target integer type before converting, avoiding both undefined behavior and silent truncation:

#include <utility>
#include <cassert>
#include <cstdint>

int main() {
    long long big = 300;
    assert(!std::in_range<int8_t>(big));    // 300 doesn't fit in a signed 8-bit type
    assert(std::in_range<int32_t>(big));     // but does fit in a 32-bit one
    return 0;
}

See Also

  • C: Numbers and Math — the same <math.h> core, plus C23’s <stdbit.h> and checked arithmetic in <stdckdint.h>.