std::ratio
Compile-time rational number arithmetic via template parameters — the unit system underlying std::chrono::duration.
std::ratiosince C++11A class template that represents a compile-time rational constant as a reduced fraction, encoding both numerator and denominator as intmax_t non-type template parameters.
Overview
std::ratio lives entirely at the type level. There are no runtime instances that hold a value — the rational number is the type. std::ratio<1, 3> and std::ratio<2, 6> are the same type after reduction; both expose ::num == 1 and ::den == 3 as static constexpr intmax_t members.
This design was introduced in C++11 specifically to make std::chrono::duration unit-safe at zero runtime cost. When you write std::chrono::milliseconds, its second template argument is std::milli — an alias for std::ratio<1, 1000>. Conversions between durations reduce to a ratio multiplication performed entirely by the compiler.
The header is <ratio>.
Syntax
namespace std {
template <intmax_t N, intmax_t D = 1>
class ratio {
public:
static constexpr intmax_t num; // N / gcd(N,D), with sign of N*D
static constexpr intmax_t den; // |D| / gcd(N,D)
using type = ratio<num, den>;
};
}Both template arguments must be intmax_t values. D must not be zero. The instantiation is ill-formed if the reduced numerator or denominator would overflow intmax_t.
Predefined SI prefix aliases (C++11):
using std::atto = std::ratio<1, 1'000'000'000'000'000'000LL>; // 10^-18
using std::femto = std::ratio<1, 1'000'000'000'000'000LL>; // 10^-15
using std::pico = std::ratio<1, 1'000'000'000'000LL>; // 10^-12
using std::nano = std::ratio<1, 1'000'000'000LL>; // 10^-9
using std::micro = std::ratio<1, 1'000'000LL>; // 10^-6
using std::milli = std::ratio<1, 1'000LL>; // 10^-3
using std::centi = std::ratio<1, 100LL>; // 10^-2
using std::deci = std::ratio<1, 10LL>; // 10^-1
using std::deca = std::ratio< 10, 1LL>; // 10^1
using std::hecto = std::ratio< 100, 1LL>; // 10^2
using std::kilo = std::ratio< 1'000, 1LL>; // 10^3
using std::mega = std::ratio< 1'000'000, 1LL>; // 10^6
using std::giga = std::ratio< 1'000'000'000, 1LL>; // 10^9
using std::tera = std::ratio< 1'000'000'000'000, 1LL>; // 10^12
using std::peta = std::ratio< 1'000'000'000'000'000, 1LL>; // 10^15
using std::exa = std::ratio<1'000'000'000'000'000'000, 1LL>; // 10^18atto through femto and peta/exa are optional — defined only when representable by intmax_t.
Compile-time arithmetic (C++11): Each operation produces a nested ::type that is a fully reduced std::ratio:
// ratio_add, ratio_subtract, ratio_multiply, ratio_divide
template <class R1, class R2> struct ratio_add;
template <class R1, class R2> struct ratio_subtract;
template <class R1, class R2> struct ratio_multiply;
template <class R1, class R2> struct ratio_divide;
// Comparison — each exposes ::value (bool)
template <class R1, class R2> struct ratio_equal;
template <class R1, class R2> struct ratio_not_equal;
template <class R1, class R2> struct ratio_less;
template <class R1, class R2> struct ratio_greater;
template <class R1, class R2> struct ratio_less_equal;
template <class R1, class R2> struct ratio_greater_equal;Examples
Basic ratio inspection
#include <ratio>
#include <iostream>
int main() {
using a_third = std::ratio<1, 3>;
using two_sixths = std::ratio<2, 6>; // same type after reduction
static_assert(std::ratio_equal<a_third, two_sixths>::value); // C++11
std::cout << a_third::num << '/' << a_third::den << '\n'; // 1/3
std::cout << two_sixths::num << '/' << two_sixths::den << '\n'; // 1/3
}Compile-time arithmetic
#include <ratio>
using two_sevenths = std::ratio<2, 7>;
using three_fourths = std::ratio<3, 4>;
using sum = std::ratio_add<two_sevenths, three_fourths>::type; // 29/28
using product = std::ratio_multiply<two_sevenths, three_fourths>::type; // 3/14
static_assert(sum::num == 29 && sum::den == 28);
static_assert(product::num == 3 && product::den == 14);Custom chrono durations
std::chrono::duration<Rep, Period> uses a std::ratio as its second template argument to describe how many seconds each tick represents.
#include <chrono>
#include <ratio>
#include <iostream>
// Film frame rate: one tick = 1/24 second
using fps24_duration = std::chrono::duration<unsigned long, std::ratio<1, 24>>;
// A "school lesson" in Germany: one tick = 45 minutes = 2700 seconds
using lesson_duration = std::chrono::duration<double, std::ratio<2700>>;
int main() {
fps24_duration one_second_of_film{24};
lesson_duration half_lesson{0.5};
// Convert fps24 -> milliseconds
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(one_second_of_film);
std::cout << ms.count() << " ms\n"; // 1000 ms
// lesson_duration to seconds requires explicit cast (no implicit lossy conversion)
auto secs = std::chrono::duration_cast<std::chrono::seconds>(half_lesson);
std::cout << secs.count() << " s\n"; // 1350 s
}Type-level unit arithmetic in generic code
#include <ratio>
#include <type_traits>
// A quantity type parameterised by its unit (as a ratio relative to some base)
template <typename Rep, typename Unit>
struct quantity {
Rep value;
};
// Convert between two quantity types with the same base unit
template <typename ToUnit, typename Rep, typename FromUnit>
auto unit_cast(quantity<Rep, FromUnit> q) {
// Conversion factor: FromUnit / ToUnit, computed at compile time
using factor = std::ratio_divide<FromUnit, ToUnit>;
return quantity<Rep, ToUnit>{
static_cast<Rep>(q.value * factor::num / factor::den)
};
}
using meters = std::ratio<1>;
using centimeters = std::ratio<1, 100>;
using kilometers = std::ratio<1000>;
int main() {
quantity<double, kilometers> dist{1.5};
auto in_meters = unit_cast<meters>(dist); // 1500.0
auto in_cm = unit_cast<centimeters>(dist); // 150000.0
}Best Practices
Let the library reduce for you. Never manually compute GCDs. Pass any valid N/D pair and rely on the guaranteed reduction — std::ratio<6, 9> gives you num == 2, den == 3 without effort.
Express domain units as type aliases. Naming using fps24 = std::ratio<1, 24> once and reusing it is far clearer than scattering std::ratio<1, 24> literals throughout duration declarations.
Prefer ratio_multiply/ratio_divide over manual calculation. When building compound units, compose existing ratio types rather than computing the final fraction by hand. The compiler catches overflow; you won't.
Use static_assert for ratio constraints. If a template requires a ratio smaller than std::ratio<1> (i.e., a sub-second period), encode that requirement explicitly:
template <typename Rep, typename Period>
void requires_sub_second(std::chrono::duration<Rep, Period>) {
static_assert(std::ratio_less<Period, std::ratio<1>>::value,
"Period must be finer than one second");
}Common Pitfalls
Assuming value semantics. std::ratio has no constructor and no data members. Attempting to instantiate an object and store a runtime fraction in it is a misuse — use a std::pair<intmax_t, intmax_t> or a custom rational class for runtime values.
Overflow in deeply nested arithmetic. Each arithmetic operation reduces to lowest terms, but intermediate values must still fit in intmax_t. A chain of multiplications on large ratios can produce a hard compile error (std::ratio is required to be ill-formed on overflow). Factor your ratios before composing.
Misreading ::num and ::den on negative ratios. The sign is always canonicalized onto ::num; ::den is always positive. std::ratio<-3, -5> gives num == 3, den == 5. std::ratio<3, -5> gives num == -3, den == 5.
Implicit duration conversions that lose precision. std::chrono deliberately blocks implicit conversion from a coarser to a finer duration type when Rep is integral. The underlying check uses ratio comparison. If you get a compilation error converting between durations, use duration_cast and understand you are accepting truncation.
See Also
std::chrono::duration— the primary consumer ofstd::ratio; itsPeriodtemplate argument is astd::ratiospecifying seconds per tickstd::chrono::time_point— pairs a clock with aduration, itself parameterised on astd::ratio<type_traits>— the broader metaprogramming vocabulary that ratio arithmetic complements
Dimensional Analysis Type System
The unit_cast sketch above converts scalars in a single physical dimension. std::ratio can go further: by encoding dimension exponents as extra template parameters, you can enforce at compile time that velocity = length / time, preventing nonsensical operations like adding a length to a time.
// C++11
#include <ratio>
// A quantity carries a value plus the exponents of its base dimensions.
// Exponents are intmax_t ratios so fractional dimensions (e.g. sqrt(m)) work too.
template <typename Rep,
typename ScaleFactor, // ratio relative to SI base unit
int LengthExp = 0, // exponent for Length (metres)
int TimeExp = 0, // exponent for Time (seconds)
int MassExp = 0> // exponent for Mass (kilograms)
struct quantity {
Rep value;
};
// Multiply two quantities: add exponents, multiply scale factors
template <typename Rep,
typename S1, int L1, int T1, int M1,
typename S2, int L2, int T2, int M2>
auto operator*(quantity<Rep, S1, L1, T1, M1> a,
quantity<Rep, S2, L2, T2, M2> b)
{
using NewScale = std::ratio_multiply<S1, S2>;
return quantity<Rep, NewScale, L1+L2, T1+T2, M1+M2>{ a.value * b.value };
}
// Divide two quantities: subtract exponents, divide scale factors
template <typename Rep,
typename S1, int L1, int T1, int M1,
typename S2, int L2, int T2, int M2>
auto operator/(quantity<Rep, S1, L1, T1, M1> a,
quantity<Rep, S2, L2, T2, M2> b)
{
using NewScale = std::ratio_divide<S1, S2>;
return quantity<Rep, NewScale, L1-L2, T1-T2, M1-M2>{ a.value / b.value };
}
// Convenient type aliases
// Lengths
using meters = quantity<double, std::ratio<1>, 1, 0, 0>;
using nanometers = quantity<double, std::nano, 1, 0, 0>;
using kilometers = quantity<double, std::kilo, 1, 0, 0>;
// Times
using seconds_q = quantity<double, std::ratio<1>, 0, 1, 0>;
using millisec_q = quantity<double, std::milli, 0, 1, 0>;
// Compound dimensions
// velocity: length^1 * time^-1
// acceleration: length^1 * time^-2
int main() {
meters dist{100.0};
seconds_q t{9.58}; // Usain Bolt 100 m world record
auto velocity = dist / t;
// velocity has type quantity<double, ratio<1>, 1, -1, 0> — metres per second
// Adding a plain "meters" value here would be a compile error: different dimension
static_assert(decltype(velocity)::value == decltype(velocity)::value); // placeholder
// velocity.value ≈ 10.44 m/s
// Nanometre-scale example
nanometers wavelength{550.0}; // visible green light
// wavelength has scale std::nano relative to the metre base —
// no runtime conversion needed; scale is tracked in the type
}The real power emerges when you try to call a function that expects quantity<…, 1, -1, 0> (velocity) and accidentally pass a quantity<…, 1, 0, 0> (length) — the compiler rejects it without any runtime check.
is_ratio Concept / Detection
The standard library provides no built-in is_ratio trait. Generic code that accepts arbitrary Period arguments often needs to guard against non-ratio types. A partial-specialization trait covers the pattern cleanly.
// C++11
#include <ratio>
#include <type_traits>
template <typename T>
struct is_ratio : std::false_type {};
template <intmax_t N, intmax_t D>
struct is_ratio<std::ratio<N, D>> : std::true_type {};
template <typename T>
inline constexpr bool is_ratio_v = is_ratio<T>::value; // C++17 variable templateWith this trait in hand you can write constrained templates that give a clean error message instead of an obscure instantiation failure deep inside the standard library.
// C++20
#include <concepts>
template <typename Period>
requires is_ratio_v<Period>
void print_period() {
std::cout << Period::num << '/' << Period::den << '\n';
}
// In C++11/14/17, use static_assert instead:
template <typename Period>
void print_period_compat() {
static_assert(is_ratio_v<Period>,
"Period must be a std::ratio specialisation"); // C++17
std::cout << Period::num << '/' << Period::den << '\n';
}
// Usage
print_period<std::milli>(); // 1/1000
print_period<std::ratio<3,7>>(); // 3/7
// print_period<int>(); // compile error: constraint not satisfiedThis pattern is common in libraries that build on top of std::chrono and accept user-supplied period types.
Ratio Overflow Error Messages
std::ratio arithmetic is required by the standard to be ill-formed when an intermediate or final value would overflow intmax_t. The error surfaces as a static_assert inside the standard library headers, typically with a message like:
error: static assertion failed: ratio overflow
static_assert(result != 0 || ..., "ratio overflow");A concrete example that always overflows on any conforming platform:
// C++11 — this DOES NOT COMPILE
#include <ratio>
// std::exa = ratio<1'000'000'000'000'000'000> (10^18)
// Multiplying exa * exa = 10^36, which cannot fit in intmax_t (typically 64-bit)
using overflow = std::ratio_multiply<std::exa, std::exa>; // hard errorWhen you genuinely need a number larger than intmax_t can hold, the correct approach is to bail out of the compile-time type system and perform the calculation at runtime using long double:
// C++11 — runtime fallback for oversized ratios
#include <ratio>
#include <cstdint>
// Convert any ratio to a long double at runtime
template <typename R>
constexpr long double ratio_to_ld() {
return static_cast<long double>(R::num) / static_cast<long double>(R::den);
}
// Multiply two ratios as floating-point values without overflow
template <typename R1, typename R2>
constexpr long double ratio_product_ld() {
return ratio_to_ld<R1>() * ratio_to_ld<R2>();
}
// 10^18 * 10^15 = 10^33 — too large for ratio_multiply but fine as long double
constexpr long double big = ratio_product_ld<std::exa, std::peta>(); // 1e33Reserve the compile-time ratio type system for values that actually fit. If your domain involves astronomical distances or sub-Planck scales, keep ratio for unit conversion factors (which are typically moderate fractions) and use long double or a big-integer library for the magnitudes themselves.
Binary SI Prefixes (IEC 80000-13)
The standard only defines decimal SI prefixes. The IEC 80000-13 binary prefixes — used correctly when discussing RAM, disk blocks, and network buffers — must be defined by the application. Each is an exact power of 1024:
// C++11 — IEC binary prefixes (not in <ratio>)
#include <ratio>
using kibi = std::ratio< 1'024LL>; // 2^10 ~ 1.02 × 10^3
using mebi = std::ratio< 1'048'576LL>; // 2^20 ~ 1.05 × 10^6
using gibi = std::ratio< 1'073'741'824LL>; // 2^30 ~ 1.07 × 10^9
using tebi = std::ratio<1'099'511'627'776LL>; // 2^40 ~ 1.10 × 10^12
// pebi = 2^50 = 1'125'899'906'842'624 — fits in intmax_t on 64-bit platforms
using pebi = std::ratio<1'125'899'906'842'624LL>; // 2^50The difference between the decimal and binary prefixes is small but cumulative. 1 kilobyte (decimal) = 1000 bytes; 1 kibibyte (binary) = 1024 bytes — a ~2.4% gap. By 1 gibibyte the gap has grown to ~7.4%.
// C++11
template <typename Rep, typename Unit>
struct storage_quantity { Rep value; };
template <typename ToUnit, typename Rep, typename FromUnit>
auto storage_cast(storage_quantity<Rep, FromUnit> q) {
using factor = std::ratio_divide<FromUnit, ToUnit>;
return storage_quantity<Rep, ToUnit>{
static_cast<Rep>(q.value * factor::num / factor::den)
};
}
// Decimal (SI) storage units
using bytes_t = std::ratio<1>;
using kilobytes_t = std::kilo; // 1000 bytes
using megabytes_t = std::mega; // 1'000'000 bytes
// Binary (IEC) storage units
using kibibytes_t = kibi; // 1024 bytes
using mebibytes_t = mebi; // 1'048'576 bytes
int main() {
// A file reported as "1.44 MB" by Windows (actually mebibytes)
storage_quantity<double, mebibytes_t> floppy{1.44};
// Convert to SI megabytes: 1.44 * 1'048'576 / 1'000'000 ≈ 1.51 MB
auto mb = storage_cast<megabytes_t>(floppy);
// mb.value ≈ 1.509
// Convert to bytes to see the raw count
auto bytes = storage_cast<bytes_t>(floppy);
// bytes.value = 1'509'949.44 — approximately 1.5 million bytes
}Always distinguish KB (decimal) from KiB (binary) in user-facing code. A storage allocation API that accepts "megabytes" but interprets them as mebibytes silently allocates ~5% more memory than expected on every call.
Duration Literal Interop
C++14 added user-defined literals for std::chrono durations, making it easy to write 1s, 500ms, 2min, and 1h directly. Each literal produces a duration whose Period is a std::ratio:
// C++14
#include <chrono>
#include <iostream>
using namespace std::chrono_literals;
int main() {
auto t1 = 1s; // std::chrono::seconds = duration<long long, ratio<1>>
auto t2 = 500ms; // std::chrono::milliseconds = duration<long long, milli>
auto t3 = 2min; // std::chrono::minutes = duration<long long, ratio<60>>
auto t4 = 1h; // std::chrono::hours = duration<long long, ratio<3600>>
// Implicit conversion is allowed when the target is coarser (no data loss)
std::chrono::seconds from_ms = 2000ms; // 2000 ms -> 2 s, exact
// Implicit conversion is BLOCKED when the target is finer (potential truncation)
// std::chrono::milliseconds bad = 1s; // compile error
auto fine = std::chrono::duration_cast<std::chrono::milliseconds>(1s); // OK
std::cout << t1.count() << " s\n"; // 1
std::cout << t2.count() << " ms\n"; // 500
std::cout << from_ms.count() << " s\n"; // 2
std::cout << fine.count() << " ms\n"; // 1000
}duration_cast between two custom periods performs a compile-time fraction multiplication using std::ratio_divide. Converting from duration<long long, ratio<1, 24>> (film frames at 24 fps) to milliseconds computes ratio_divide<milli, ratio<1,24>> = ratio<1000*24, 1> = ratio<24000> and multiplies the tick count by 24000/1 — all resolved at compile time with no division at runtime for integral reps.
// C++11 — custom-period duration_cast example
#include <chrono>
#include <ratio>
#include <iostream>
using frames_24fps = std::chrono::duration<long long, std::ratio<1, 24>>;
using frames_30fps = std::chrono::duration<long long, std::ratio<1, 30>>;
int main() {
frames_24fps film_clip{240}; // 240 frames at 24 fps = 10 seconds
// Convert to 30 fps frame count
auto at_30fps = std::chrono::duration_cast<frames_30fps>(film_clip);
std::cout << at_30fps.count() << " frames at 30fps\n"; // 300
// Underlying ratio: ratio_divide<ratio<1,30>, ratio<1,24>> = ratio<24,30> = ratio<4,5>
// So each 24fps frame becomes 30/24 = 5/4 of a 30fps frame.
// duration_cast truncates: 240 * 5/4 = 300 exactly here.
}ratio_less for Template Ordering
std::ratio_less acts as a compile-time comparator, analogous to std::less at runtime. You can use it to select the smaller (or larger) of two ratio types via template metaprogramming, without any runtime branching.
// C++11
#include <ratio>
#include <type_traits>
// Pick the smaller of two ratio types
template <typename R1, typename R2>
using ratio_min = std::conditional_t<
std::ratio_less<R1, R2>::value,
R1,
R2
>;
// Pick the larger of two ratio types
template <typename R1, typename R2>
using ratio_max = std::conditional_t<
std::ratio_less<R1, R2>::value,
R2,
R1
>;
static_assert(std::ratio_equal<ratio_min<std::milli, std::micro>, std::micro>::value);
static_assert(std::ratio_equal<ratio_max<std::milli, std::micro>, std::milli>::value);A practical use case: given a parameter pack of duration types, find the finest (smallest period) so that a sum can be accumulated without loss of precision.
// C++17 — fold over a parameter pack to find the finest period
#include <ratio>
#include <chrono>
#include <type_traits>
// Base case
template <typename... Durations>
struct finest_period;
template <typename D>
struct finest_period<D> {
using type = typename D::period;
};
template <typename D1, typename D2, typename... Rest>
struct finest_period<D1, D2, Rest...> {
using smaller = ratio_min<typename D1::period, typename D2::period>;
using type = typename finest_period<
std::chrono::duration<long long, smaller>,
Rest...
>::type;
};
// The finest period among hours, minutes, and milliseconds is milli
using result = finest_period<
std::chrono::hours,
std::chrono::minutes,
std::chrono::milliseconds
>::type;
static_assert(std::ratio_equal<result, std::milli>::value);Use ratio_less as the comparator whenever you sort or select ratio types in a metaprogram. It handles sign and magnitude correctly, including ratios greater than one (e.g., comparing std::kilo against std::mega).
C++17 _v Variable Template Helpers
C++17 added _v variable template aliases for all std::ratio comparison traits, following the same convention as std::is_same_v, std::is_integral_v, and so on. They eliminate the ::value suffix boilerplate.
// C++11 — verbose form
static_assert(std::ratio_less<std::milli, std::kilo>::value, "milli < kilo");
static_assert(std::ratio_equal<std::ratio<2,4>, std::ratio<1,2>>::value, "reduces correctly");
static_assert(std::ratio_not_equal<std::nano, std::micro>::value, "different magnitudes");
// C++17 — concise _v form
static_assert(std::ratio_less_v<std::milli, std::kilo>, "milli < kilo");
static_assert(std::ratio_equal_v<std::ratio<2,4>, std::ratio<1,2>>, "reduces correctly");
static_assert(std::ratio_not_equal_v<std::nano, std::micro>, "different magnitudes");The full set of C++17 variable templates mirrors the trait structs one-for-one:
// C++17
template <class R1, class R2>
inline constexpr bool ratio_equal_v = ratio_equal<R1,R2>::value;
template <class R1, class R2>
inline constexpr bool ratio_not_equal_v = ratio_not_equal<R1,R2>::value;
template <class R1, class R2>
inline constexpr bool ratio_less_v = ratio_less<R1,R2>::value;
template <class R1, class R2>
inline constexpr bool ratio_greater_v = ratio_greater<R1,R2>::value;
template <class R1, class R2>
inline constexpr bool ratio_less_equal_v = ratio_less_equal<R1,R2>::value;
template <class R1, class R2>
inline constexpr bool ratio_greater_equal_v = ratio_greater_equal<R1,R2>::value;Prefer the _v helpers in all new C++17 and later code. They read as boolean expressions rather than type member accesses, which makes static_assert conditions, if constexpr guards, and requires clauses noticeably easier to scan.
// C++20 — _v helpers integrate naturally into concepts and if constexpr
template <typename Period>
constexpr const char* classify_period() {
if constexpr (std::ratio_less_equal_v<Period, std::nano>)
return "sub-nanosecond or nanosecond";
else if constexpr (std::ratio_less_v<Period, std::ratio<1>>)
return "sub-second";
else if constexpr (std::ratio_equal_v<Period, std::ratio<1>>)
return "seconds";
else
return "coarser than seconds";
}
static_assert(classify_period<std::pico>() == std::string_view{"sub-nanosecond or nanosecond"});
static_assert(classify_period<std::milli>() == std::string_view{"sub-second"});
static_assert(classify_period<std::ratio<1>>() == std::string_view{"seconds"});
static_assert(classify_period<std::ratio<3600>>() == std::string_view{"coarser than seconds"});