Chapter 25: Numerics, Randomness, and Time
C++ gives you precise control over numbers, random sampling, and time measurement. This chapter explains floating point behavior, fixed width integers and limits, robust random number facilities, and the modern <chrono> tools for clocks, durations, time points, and formatting.
Floating point behavior and pitfalls
Floating point types model real numbers with finite precision. Operations round to the nearest representable value, which means algebraic identities do not always hold. Correct code accepts small error, compares with tolerances, and uses the right functions for classification and rounding.
Representation and rounding
float, double, and long double store numbers as sign, exponent, and significand. Each operation rounds to fit that model. Small gaps between adjacent representable values get larger as magnitudes grow, so adding a tiny number to a huge number may do nothing.
double x = 1e16;
double y = 1.0;
double z = (x + y) - x; // z may be 0.0 because y was lost to rounding
Equality and tolerances
Check closeness, not exact equality. Use absolute tolerances for small magnitudes and relative tolerances for large magnitudes. Blend both for general code.
bool nearly_equal(double a, double b, double rel=1e-12, double abs=1e-15) {
double diff = std::fabs(a - b);
return diff <= std::max(rel * std::max(std::fabs(a), std::fabs(b)), abs);
}
std::fma when available to compute a * b + c with a single rounding; this improves stability in many kernels.
Special values and classification
IEEE arithmetic supports infinities and NaNs. Use classification functions to branch safely and avoid undefined behavior in edge cases.
#include <cmath>
double q = 0.0, r = -0.0;
bool s1 = std::isnan(0.0/0.0); // true
bool s2 = std::isinf(1.0/0.0); // true
bool s3 = std::signbit(r); // true for negative zero
std::isnan to detect it. Propagation rules vary by operation, so treat NaN as contagious.
Stable summation
Summing many values can accumulate error. A compensated algorithm reduces drift by tracking lost low order bits and feeding them back on the next step.
double kahan_sum(const std::vector<double>& v) {
double sum = 0.0, c = 0.0;
for (double x : v) {
double y = x - c;
double t = sum + y;
c = (t - sum) - y;
sum = t;
}
return sum;
}
Numeric limits and fixed width integers
When you need exact sizes and boundaries, use fixed width types and query their properties through std::numeric_limits. This keeps code portable and correct across platforms and compilers.
Choosing integer widths
Pick the smallest type that holds the full range of your data; this reduces memory traffic and keeps intent clear. Use signed types when negative values occur, otherwise prefer unsigned only for bit masks and modular arithmetic.
| Family | Header | Examples | Typical use |
| Fixed width | <cstdint> | int8_t, uint32_t, int64_t | Binary formats, hashes |
| Fast width | <cstdint> | int_fast16_t, … | Speed on given platform |
| Least width | <cstdint> | int_least32_t, … | Minimum bits with minimal size |
| Size types | <cstddef> | size_t, ptrdiff_t | Indices and pointer diffs |
Querying limits and properties
Use std::numeric_limits<T> to discover min, max, epsilon, and behavior flags. This avoids hardcoded constants and covers floating point details.
#include <limits>
auto maxu32 = std::numeric_limits<uint32_t>::max();
auto epsd = std::numeric_limits<double>::epsilon();
bool is_ieee = std::numeric_limits<double>::is_iec559; // IEEE 754?
Safe conversions and saturation
Converting between widths can overflow or truncate. Validate ranges before casting, or apply a saturating clamp for defensive code.
#include <algorithm>
uint8_t to_u8_saturate(int x) {
return static_cast<uint8_t>(std::clamp(x, 0, 255));
}
Random number engines and distributions
The <random> library separates engines that produce raw bits from distributions that shape values. You seed an engine, then feed it through a distribution that models the desired behavior.
Engines and seeding
std::mt19937 is a common workhorse. Seed once from std::random_device or from a fixed seed for reproducible experiments. Keep engines in objects or thread local storage to avoid global contention.
#include <random>
std::mt19937 make_rng() {
std::random_device rd;
std::seed_seq seq{rd(), rd(), rd(), rd()};
return std::mt19937{seq};
}
Uniform, normal, and discrete choices
Apply distributions to map engine output into meaningful values. Uniform distributions sample ranges, normal distributions model bell curves, and discrete distributions pick from weighted buckets.
auto rng = make_rng();
std::uniform_int_distribution<int> die(1, 6);
std::uniform_real_distribution<double> unit(0.0, 1.0);
std::normal_distribution<double> noise(0.0, 1.0);
std::discrete_distribution<int> pick({1, 3, 1}); // weights
int d = die(rng);
double x = unit(rng);
double n = noise(rng);
int ix = pick(rng);
thread_local std::mt19937 rng or pass engines down the call chain to keep results predictable and fast.
Reproducibility and serialization
Deterministic runs are vital for tests. Save and restore engine state; then rerun with the same sequence later.
std::mt19937 rng = make_rng();
std::string state;
{
std::ostringstream os;
os << rng;
state = os.str();
}
int a = std::uniform_int_distribution<int>(0, 9)(rng);
// restore
{
std::istringstream is(state);
is >> rng;
}
int b = std::uniform_int_distribution<int>(0, 9)(rng); // matches prior path after restore
chrono clocks, durations, and time points
<chrono> measures time with strong types. A clock tells the current time, a duration represents a length of time, and a time_point binds a clock to a specific instant.
Clocks you will use
system_clock tracks civil time and can jump when the system time is adjusted. steady_clock is monotonic and does not jump, which makes it ideal for measuring intervals. high_resolution_clock is an alias to the highest resolution clock available on your platform.
#include <chrono>
using namespace std::chrono;
auto t0 = steady_clock::now();
// work …
auto ms = duration_cast<milliseconds>(steady_clock::now() - t0);
Durations and literals
Durations carry both representation and period. Use suffixes to write readable code. Convert with duration_cast when narrowing or when you need a specific unit.
using namespace std::chrono_literals;
auto wait = 150ms + 2s;
auto us = std::chrono::duration_cast<std::chrono::microseconds>(wait);
Time points and arithmetic
Add durations to time points, or subtract time points to get durations. Convert between clocks only through approved utilities such as clock_cast when available.
auto start = std::chrono::steady_clock::now();
// …
auto elapsed = std::chrono::steady_clock::now() - start; // duration
steady_clock for benchmarking. system_clock can move backwards or forwards if the system time is adjusted, which breaks elapsed time math.
Date and time formatting
C++20 adds std::format support for time types plus full date formatting in <chrono>. You can print durations and time points using familiar format strings, and you can work with time zones through zoned_time and the IANA database when your standard library provides it.
Formatting durations
Formatters understand durations directly. Choose units with a cast before formatting to control the output.
#include <format>
using namespace std::chrono_literals;
auto d = 12345ms;
auto s = std::format("{} ms", std::chrono::duration_cast<std::chrono::milliseconds>(d).count());
Formatting time points
Convert a time point to calendar form with floor or use std::chrono::format with specifiers that mirror strftime patterns. The braces carry the pattern; options go inside as {…}.
#include <chrono>
#include <format>
using namespace std::chrono;
auto now = system_clock::now();
std::string iso = std::chrono::format("{:%Y-%m-%dT%H:%M:%S%Ez}", now);
Time zones and zoned_time
Bind a clock time to a named zone for correct civil time handling, including daylight saving rules. This requires a time zone database at runtime, which many standard libraries ship.
#include <chrono>
using namespace std::chrono;
zoned_time zt{"Europe/London", system_clock::now()};
auto local = std::chrono::format("{:%F %T %Z}", zt); // prints date, time, and zone
Parsing dates from strings
Use std::chrono::parse to turn text into time points or durations. Match the pattern to your input exactly to avoid failure.
#include <chrono>
#include <sstream>
using namespace std::chrono;
std::istringstream is{"2025-11-06 20:00"};
sys_seconds tp;
is >> parse("%F %R", tp); // tp holds a UTC time_point at minute resolution