5

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Marinos K
  • 1,779
  • 16
  • 39
  • Most of the questions answered in documentation: [`std::chrono::duration`](http://en.cppreference.com/w/cpp/chrono/duration). – m0nhawk Feb 05 '16 at 10:55
  • 1
    Relevant bits: `count()` is not marked `noexcept`, `Note: each of the predefined duration types covers a range of at least ±292 years.` – melak47 Feb 05 '16 at 14:04

1 Answers1

12

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

Community
  • 1
  • 1
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Can you explain the output of `-292277024` to `292277024` years? It appears to contradict the fact that 45 bits of milliseconds only gets you ~557.5 years – AndyG Feb 05 '16 at 15:36
  • Well don't I feel silly! Of course `long long` is 64 bits. Thanks. This, however, causes me to ask "Why the strange minimum bits in the standard?" microseconds is 55, hours is 23, minutes is 29. It feels arbitrary. – AndyG Feb 05 '16 at 15:57
  • @AndyG: Good question. The algorithm I used is that all types should have a range at least as big as the nanoseconds type, and nanoseconds should be based on the largest signed integral type guaranteed to be in the standard (which is 64 bits, +/- 292 years). If milliseconds was only 44 bits, its range would be smaller than that: +/-278 years. In hindsight, this algorithm is sub-optimal, and I should have required a range of at least +-32767 years for everything but nanoseconds. In practice, the only thing this would have changed is minutes (from 32 bits to 64 bits). – Howard Hinnant Feb 05 '16 at 16:04
  • If, theoretically, we had types that exactly matched the minimums specified in the standard, then we get 35 signed bits to represent seconds, and only 29 to represent minutes. If we performed a `duration_cast` of `std::chrono::seconds::max()` to `std::chrono::minutes`, wouldn't we get overflow? Because `(2^34) / 60 > (2^28)` – AndyG Feb 05 '16 at 16:12
  • @AndyG: Yes you would overflow. There is no way (that I'm aware of) to "equalize" the ranges of these different types. The underlying theme is to try to make the ranges sufficiently large so as to make overflow extremely unlikely. E.g. If you are trying to measure with nanosecond precision over a period of centuries, you are likely already off in the weeds for problems other than overflow. – Howard Hinnant Feb 05 '16 at 16:22
  • seconds must have at least 35 bits. – Howard Hinnant Feb 05 '16 at 16:23
  • I think at a minimum the standard should guarantee that casting the maximum of any of the "convenience typedefs" (nanoseconds, microseconds, milliseconds, seconds, minutes, hours) from a lower order to a higher order should not overflow. – AndyG Feb 05 '16 at 16:24
  • Sorry, I misread "minutes (from 32 bits to 64 bits)" as "seconds (from 32 bits to 64 bits)" – AndyG Feb 05 '16 at 16:25
  • Thanks for lending me your time! Will submit a request. – AndyG Feb 05 '16 at 16:27