std::ratio in C++: Compile-Time Rational Numbers
This article provides an overview of std::ratio, a
utility introduced in C++11 to represent and manipulate exact rational
numbers at compile time. It examines the fundamental purpose of
std::ratio, its core mechanics including automatic fraction
reduction and compile-time arithmetic, and its primary real-world
application as the foundational scaling factor for type-safe time
representations in the standard library.
What is std::ratio?
Defined in the <ratio> header,
std::ratio is a class template designed to represent an
exact compile-time fraction:
template <std::intmax_t Num, std::intmax_t Denom = 1>
class ratio;It stores the numerator (Num) and denominator
(Denom) as std::intmax_t constant expressions.
Because it operates entirely at compile time, it does not exist as an
instantiated object that consumes runtime memory or execution cycles;
instead, it is a type that encodes a specific rational value.
Preventing Floating-Point Inaccuracies
The primary motivation for std::ratio is to eliminate
the imprecision and non-determinism associated with floating-point
arithmetic. Standard types like float and
double cannot precisely represent many fractional values
(such as 1/3 or 1/10 in binary representation) and accumulate rounding
errors over repetitive operations.
By treating fractions symbolically as pairs of integers,
std::ratio preserves absolute mathematical precision
throughout template computations.
Automatic Reduction and Normalization
When a std::ratio type is instantiated, the compiler
immediately reduces the fraction to its lowest terms using the greatest
common divisor (GCD). It also normalizes the sign so that the
denominator is always positive:
std::ratio<2, 4>automatically normalizes tostd::ratio<1, 2>.std::ratio<1, -3>automatically normalizes tostd::ratio<-1, 3>.
This ensures that equivalent fractions produce identical types, enabling robust type-checking and comparisons.
Compile-Time Arithmetic and Comparisons
The <ratio> header provides template aliases for
common arithmetic operations, evaluated entirely by the compiler:
std::ratio_add: Computes the sum of two ratios.std::ratio_subtract: Computes the difference.std::ratio_multiply: Computes the product.std::ratio_divide: Computes the quotient.
Additionally, comparison templates such as
std::ratio_equal, std::ratio_less, and
std::ratio_greater allow static assertions and conditional
compilation based on the relative size of rational numbers. Arithmetic
operations are safely implemented to prevent integer overflow wherever
mathematically possible.
Foundation for std::chrono
The most prominent use case for std::ratio within the
C++ standard library is std::chrono::duration. A duration
consists of a tick count combined with a tick period represented by a
std::ratio:
std::chrono::millisecondsusesstd::ratio<1, 1000>.std::chrono::minutesusesstd::ratio<60, 1>.
This architecture allows the compiler to enforce unit safety, convert between different units with exact conversion factors, and reject potentially lossy implicit conversions at compile time.
Standard SI Prefixes
To simplify conversions across physical units, C++ provides predefined type aliases for metric (SI) prefixes, such as:
std::nano(std::ratio<1, 1000000000>)std::micro(std::ratio<1, 1000000>)std::milli(std::ratio<1, 1000>)std::kilo(std::ratio<1000, 1>)std::mega(std::ratio<1000000, 1>)
These aliases eliminate magic numbers and ensure consistency across libraries performing dimensional analysis or metric scaling.