Skip to content
C++
Library
since C++11
Intermediate

std::ratio

Compile-time rational number arithmetic via template parameters — the unit system underlying std::chrono::duration.

std::ratiosince C++11

A 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

cpp
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):

cpp
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^18

atto 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:

cpp
// 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

cpp
#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

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

cpp
#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

cpp
#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:

cpp
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 of std::ratio; its Period template argument is a std::ratio specifying seconds per tick
  • std::chrono::time_point — pairs a clock with a duration, itself parameterised on a std::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.

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

cpp
// 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 template

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

cpp
// 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 satisfied

This 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:

cpp
error: static assertion failed: ratio overflow
   static_assert(result != 0 || ..., "ratio overflow");

A concrete example that always overflows on any conforming platform:

cpp
// 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 error

When 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:

cpp
// 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>(); // 1e33

Reserve 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:

cpp
// 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^50

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

cpp
// 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:

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

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

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

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

cpp
// 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:

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

cpp
// 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"});