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>.