Howard Hinnant
2016-01-01

<chrono> Utilities

A few simple utility functions for working with durations.

Rounding functions

namespace detail {

template <class T>
struct choose_trunc_type
{
    static const int digits = std::numeric_limits<T>::digits;
    using type = typename std::conditional
                 <
                     digits < 32,
                     std::int32_t,
                     typename std::conditional
                     <
                         digits < 64,
                         std::int64_t,
#ifdef __SIZEOF_INT128__
                         __int128
#else
                         std::int64_t
#endif
                     >::type
                 >::type;
};

template <class T>
constexpr
inline
typename std::enable_if
<
    !std::chrono::treat_as_floating_point<T>::value,
    T
>::type
trunc(T t) NOEXCEPT
{
    return t;
}

template <class T>
constexpr
inline
typename std::enable_if
<
    std::chrono::treat_as_floating_point<T>::value,
    T
>::type
trunc(T t) NOEXCEPT
{
    using namespace std;
    using I = typename choose_trunc_type<T>::type;
    constexpr auto digits = numeric_limits<T>::digits;
    static_assert(digits < numeric_limits<I>::digits, "");
    constexpr auto max = I{1} << (digits-1);
    constexpr auto min = -max;
    const auto negative = t < T{0};
    if (min <= t && t <= max && t != 0 && t == t)
    {
        t = static_cast<T>(static_cast<I>(t));
        if (t == 0 && negative)
            t = -t;
    }
    return t;
}

}  // detail

// trunc towards zero
template <class To, class Rep, class Period>
constexpr
inline
To
trunc(const std::chrono::duration<Rep, Period>& d)
{
    return To{detail::trunc(std::chrono::duration_cast<To>(d).count())};
}

// round down
template <class To, class Rep, class Period>
constexpr
inline
To
floor(const std::chrono::duration<Rep, Period>& d)
{
    auto t = trunc<To>(d);
    if (t > d)
        return t - To{1};
    return t;
}

// round to nearest, to even on tie
template <class To, class Rep, class Period>
constexpr
inline
To
round(const std::chrono::duration<Rep, Period>& d)
{
    auto t0 = floor<To>(d);
    auto t1 = t0 + To{1};
    if (t1 == To{0} && t0 < To{0})
        t1 = -t1;
    auto diff0 = d - t0;
    auto diff1 = t1 - d;
    if (diff0 == diff1)
    {
        if (t0 - trunc<To>(t0/2)*2 == To{0})
            return t0;
        return t1;
    }
    if (diff0 < diff1)
        return t0;
    return t1;
}

// round up
template <class To, class Rep, class Period>
constexpr
inline
To
ceil(const std::chrono::duration<Rep, Period>& d)
{
    auto t = trunc<To>(d);
    if (t < d)
        return t + To{1};
    return t;
}

// trunc towards zero
template <class To, class Clock, class FromDuration>
constexpr
inline
std::chrono::time_point<Clock, To>
trunc(const std::chrono::time_point<Clock, FromDuration>& tp)
{
    using std::chrono::time_point;
    return time_point<Clock, To>{trunc<To>(tp.time_since_epoch())};
}

// round down
template <class To, class Clock, class FromDuration>
constexpr
inline
std::chrono::time_point<Clock, To>
floor(const std::chrono::time_point<Clock, FromDuration>& tp)
{
    using std::chrono::time_point;
    return time_point<Clock, To>{floor<To>(tp.time_since_epoch())};
}

// round to nearest, to even on tie
template <class To, class Clock, class FromDuration>
constexpr
inline
std::chrono::time_point<Clock, To>
round(const std::chrono::time_point<Clock, FromDuration>& tp)
{
    using std::chrono::time_point;
    return time_point<Clock, To>{round<To>(tp.time_since_epoch())};
}

// round up
template <class To, class Clock, class FromDuration>
constexpr
inline
std::chrono::time_point<Clock, To>
ceil(const std::chrono::time_point<Clock, FromDuration>& tp)
{
    using std::chrono::time_point;
    return time_point<Clock, To>{ceil<To>(tp.time_since_epoch())};
}

The beauty of the chrono library is the ease and accuracy with which such conversions can be made. For example to convert from milliseconds (1/1000 of a second), to 1/30 of a second, one must multiply the milliseconds by 0.03. It is common knowledge that you can't exactly represent 0.03 in a computer. Nevertheless round will exactly (with no round off error) detect a tie and round to even when this happens. The differences diff0 and diff1 are not approximate, but exact differences, even when d has the units of millisecond and To is 1/30 of a second. The unit of diff0 and diff1 is 1/3000 of a second which both millisecond and 1/30 of a second exactly convert to (with no truncation error).

Similarly, the comparison t < d in ceil is exact, even when there is no exact conversion between t and d.

Example use of rounding functions

#include <iostream>
#include <chrono_io>

int main()
{
    using namespace std::chrono;
    milliseconds ms(2500);
    std::cout << floor<seconds>(ms) << '\n';
    std::cout << round<seconds>(ms) << '\n';
    std::cout << ceil<seconds>(ms) << '\n';
    ms = milliseconds(2516);
    typedef duration<long, std::ratio<1, 30>> frame_rate;
    std::cout << floor<frame_rate>(ms) << '\n';
    std::cout << round<frame_rate>(ms) << '\n';
    std::cout << ceil<frame_rate>(ms) << '\n';
}

2 seconds
2 seconds
3 seconds
75 [1/30]seconds
75 [1/30]seconds
76 [1/30]seconds

An interesting exercise is to find out how many frames go by (at 1/30 second frame rate) in 2516 milliseconds using xtime from C1x (without using the chrono library! :-)).

abs

template <class Rep, class Period,
          class = typename std::enable_if
          <
              std::numeric_limits<Rep>::is_signed
          >::type>
CONSTCD11
std::chrono::duration<Rep, Period>
abs(std::chrono::duration<Rep, Period> d)
{
    return d >= d.zero() ? d : -d;
}

Note that abs is disabled for unsigned durations. My reasoning is that if you try to call abs with an unsigned duration, the chances are high that there is a logical mistake in your code, and so a compile-time diagnostic is better, than a silent identity function.