Search code examples
c++c++11integer-overflowc++-chrono

Overflow in std::chrono::duration for remaining time measurement


I would like to know, how much time is remaining for an event to happen. I'm using boost::chrono or std::chrono for that.

Basically the algorithm is like that:

using std::chrono; // Just for this example
auto start = steady_clock::now();
seconds myTotalTime(10);
while(true){
  auto remaining = start + myTotalTime - steady_clock::now();
  seconds remainingSecs = duration_cast<seconds>(remaining);
  if(remainingSecs.count() <= 0) return true;
  else print("Remaining time = " + remainingSecs.count()); // Pseudo-Code!
}

Now the following could (theoretically) happen: start is near the end of the period of the clock (IMO there is no notion what 0 means, so it could be arbitrary).
Then start + myTotalTime could overflow and the subtraction could underflow.

Even simple things like steady_clock::now() - start could underflow.

For unsigned types this is not a problem. If they were unsigned, then the standard guarantees that I still get the correct number of "units" for steady_clock::now() - start in the case of an overflowed now() and the underflowing subtraction: 10 - 250 = 10 + 256 - 255 = 16 for 8bit unsigned math.

But AFAIK over/underflow for signed types is undefined behaviour.

Am I missing anything? Why are the duration and especially time_points defined with signed instead of unsigned types?


Solution

  • I don't see the problem. You choose the units such that they can't overflow for your needs, surely?

    On my system (yes, I know, but) std::chrono::seconds is defined as int64_t, so assuming a sensible epoch is chosen (!= big bang), it won't overflow any time soon.

    The others are too, but in nanoseconds that wipes 30 bits off, leaving a good chance of overflow, The remaining 33 bits give /only/ 270 years or so.

    time_point internally uses a duration, as it is duration since epoch. In principle, if your epoch is 1970 (Unix), you may want to refer to dates before then, so it has to be signed. If your epoch is 1600 (Windows), you clearly can't use nanoseconds to represent current time points, which is a portability issue.

    You can define your own duration using double, of course, where range is exchanged for resolution, and I have done this in the past as a convenient way of printing decimal second timestamps.

    Or you could define a larger integer class if you need both range and resolution.