Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

<chrono> overflow guarantees

I have this piece of code:

auto time_point_a = std::chrono::high_resolution_clock::now();
while (true) {
  auto time_point_b = std::chrono::high_resolution_clock::now();
  auto counter_ms = std::chrono::duration_cast<std::chromo::milliseconds(time_point_b - time_point_a);
  // more code
std::cont << counter_ms.count() << std::endl;
}

Is counter_ms.count() guaranteed to always return a valid value? Is there any chance that count() throws? What happens if counter_ms exceeds the size of its underlying integral type (I reckon it's long long)? My program will run for several days in a row and I need to know what happens if/when counter_ms gets TOO big.

like image 206
Marinos K Avatar asked Feb 05 '16 10:02

Marinos K


1 Answers

Is counter_ms.count() guaranteed to always return a valid value?

counter_ms holds a single signed integral count of milliseconds. The .count() member function is specified to do nothing but return this signed integral value.

Is there any chance that count() throws?

This member function is not marked noexcept for two reasons:

  1. noexcept has been used very sparingly in the std::lib.
  2. In general, durations are allowed to be based on arithmetic emulators, which might have a throwing copy constructor.

In the case of counter_ms, the representation has to be a signed integral type, which of course can not throw on copy construction.

No chance that this throws.

What happens if counter_ms exceeds the size of its underlying integral type (I reckon it's long long)?

You can inspect the underlying integral type with this program:

#include <chrono>
#include <iostream>
#include "type_name.h"

int
main()
{
    std::cout << type_name<std::chrono::milliseconds::rep>() << '\n';
}

Where "type_name.h" is described here. For me this program outputs:

long long

The standard spec says that this type must be a signed integral type of at least 45 bits. This gives it a range of at least +/- 557 years. You can find the actual range of your implementation of milliseconds with this program:

#include <chrono>
#include <iostream>

int
main()
{
    using days = std::chrono::duration
        <int, std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>>;
    using years = std::chrono::duration
        <int, std::ratio_multiply<std::ratio<146097, 400>, days::period>>;

    std::cout << std::chrono::duration_cast<years>
        (std::chrono::milliseconds::min()).count() << " years\n";
    std::cout << std::chrono::duration_cast<years>
        (std::chrono::milliseconds::max()).count() << " years\n";
}

which for me outputs:

-292277024 years
 292277024 years

By coincidence, I am the one that implemented the <chrono> implementation I am using (libc++). And the reason that the actual range is so much greater than the required minimum range is that I had trouble locating a 45 bit signed integral type, and had to settle for a 64 bit signed integral type.

When this range is exceeded, you will get the exact same behavior as signed integral arithmetic overflow (which is specified to be undefined behavior).

like image 123
Howard Hinnant Avatar answered Nov 17 '22 05:11

Howard Hinnant