剩余时间测量的 std::chrono::duration 溢出

Fla*_*ire 2 c++ integer-overflow c++11 c++-chrono

我想知道,还有多少时间让事件发生。我为此使用 boost::chrono 或 std::chrono 。

基本上算法是这样的:

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!
}
Run Code Online (Sandbox Code Playgroud)

现在(理论上)可能会发生以下情况:start接近时钟周期的末尾(IMO 不知道这0意味着什么,所以它可能是任意的)。
然后start + myTotalTime可能溢出,减法可能下溢。

即使是简单的事情,如steady_clock::now() - start可能下溢。

对于无符号类型,这不是问题。如果它们是无符号的,那么标准保证steady_clock::now() - start在溢出now()和下溢减法的情况下我仍然得到正确数量的“单位” :10 - 250 = 10 + 256 - 255 = 16对于 8 位无符号数学。

但是有符号类型的 AFAIK 上溢/下溢是未定义的行为。

我错过了什么吗?为什么持续时间,尤其是 time_points 是用有符号而不是无符号类型定义的?

How*_*ant 5

这是一个简短的程序,您可以使用它来查看steady_clock任何平台的剩余范围(相对于现在):

#include <chrono>
#include <iostream>

int
main()
{
    using namespace std::chrono;
    using namespace std;
    using days = duration<int, ratio<86400>>;
    using years = duration<double, ratio_multiply<ratio<146097, 400>, days::period>>;
    cout << years{steady_clock::time_point::max() -
                  steady_clock::now()}.count() << " years to go\n";
}
Run Code Online (Sandbox Code Playgroud)

这将输出从现在开始steady_clock溢出的年数。在几个不同的平台上运行几次之后,你应该会有一种温暖的模糊感觉,除非你的程序要运行几百年,否则你不必担心。

在我的平台上(这很常见),steady_clock以纳秒为单位测量自启动以来的时间。

对我来说,这个程序输出:

292.127 years to go
Run Code Online (Sandbox Code Playgroud)

任何平台和任何不能表示now()没有溢出的时钟都不太可能被广泛采用。

为什么持续时间,尤其是 time_points 是用有符号而不是无符号类型定义的?

  • 为了durationtime_point减法不容易出错。是的,如果您减去unsigned_smaller - unsigned_larger,您确实会得到一个明确定义的结果,但该结果不太可能是程序员期望的答案(除了您给出的 time_point + duration 的示例)。

  • 因此 1970 年之前的日期时间可以用system_clock::time_point. 尽管未指定,但它是system_clock测量Unix 时间(使用各种精度)的事实上的标准。这需要持有负值来表示 1970-01-01 00:00:00 UTC 之前的时间。我目前正在尝试标准化这种现有的做法。

相反,有符号整数表示用足够多的位指定,以试图使溢出成为一个罕见的问题。纳秒精度保证具有 +/- 292 年的范围。更粗的精度将有更多的范围。当此默认值不够时,允许自定义表示。