【问题标题】:Overflow in std::chrono::duration for remaining time measurement剩余时间测量的 std::chrono::duration 溢出
【发布时间】:2018-02-09 10:10:45
【问题描述】:

我想知道,事件发生还剩多少时间。为此,我正在使用 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!
}

现在可能(理论上)发生以下情况:start 接近时钟周期的末尾(IMO 不知道0 的含义,因此它可能是任意的)。
那么start + myTotalTime 可能会溢出,减法可能会下溢。

即使像steady_clock::now() - start 这样简单的东西也可能下溢。

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

但签名类型的 AFAIK 上溢/下溢是未定义的行为。

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

【问题讨论】:

  • 我认为您必须决定要让计算机运行多长时间才能达到真正的问题。 steady_clock::now 很可能会返回您的计算机运行时间的度量。即便如此,如果您相信您的程序在一千年左右后仍会运行,那么您肯定可以检测到包装并处理它。
  • IMO 你必须权衡利弊。除非您让您的计算机运行千年,否则您不太可能出现带符号持续时间的溢出......并且因为已经设计、实现和使用了类似持续时间类型的对象(例如,表示各种单位,如体积、数据速率、 ...),有符号类型通常非常方便,即使实际单位没有负面含义。
  • 我认为这是一个非常好的问题,并引发了一些好的答案。谢谢你的提问。我也有同样的问题。

标签: c++ c++11 integer-overflow chrono


【解决方案1】:

这是一个简短的程序,您可以使用它来查看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";
}

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

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

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

292.127 years to go

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

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

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

  • 因此 1970 年之前的日期时间可以用 system_clock::time_point 表示。虽然没有具体说明,但system_clock 正在测量Unix Time(使用各种精度)是一个事实上的标准。这需要保持负值来表示 1970-01-01 00:00:00 UTC 之前的时间。我目前正在尝试将这种现有做法标准化。

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

【讨论】:

  • 绝妙的例子!在高端戴尔笔记本电脑上的 64 位 Ubuntu 14.04 LTS 上,我收到 292.277 years to go
【解决方案2】:

我没有看到问题。您选择的单元不会溢出来满足您的需要,确定吗?

在我的系统上(是的,我知道,但是)std::chrono::seconds 被定义为 int64_t,所以假设选择了一个合理的时代(!= big bang),它不会很快溢出。

其他的也是,但在纳秒内擦除了 30 位,留下了很好的溢出机会,剩下的 33 位给 /only/ 270 年左右。

time_point 在内部使用持续时间,因为它是自纪元以来的持续时间。原则上,如果您的纪元是 1970 年(Unix),您可能希望引用在那之前的日期,因此必须对其进行签名。如果您的 epoch 是 1600 (Windows),您显然不能使用纳秒来表示当前时间点,这是一个可移植性问题。

当然,您可以使用 double 定义您自己的持续时间,其中范围交换分辨率,我过去曾这样做,作为打印十进制秒时间戳的便捷方式。

如果您需要范围和分辨率,也可以定义一个更大的整数类。

【讨论】:

  • 但是可移植性正是我使用chrono 的关键。问题的原因是在使用boost::chrono(C++11 之前的代码)期间出现了一种不可能的错误,其中与上述类似的代码失败了。我正在使用 boost/std 标准中定义的 time_pointseconds。所以没有自己的reps。但我确实想参考与稳定时钟now() 相当接近的 time_points。时代是一个不应该打扰我的实现细节。但如果例如windows 决定将 epoch 设置为 1600 并使用纳秒的 time_point 周期,那么由于 UB 我会遇到问题。那我该怎么办?
  • IMO 唯一的办法就是使用time_point&lt;Clock, seconds&gt; 并使用time_point_casts。因为我们不能依赖时钟给出几秒钟左右的时间。顺便说一句:你在 windows 上的 1600 epoch 有参考吗?
  • 我刚刚搜索了 windows 和 epoch。没有探索。我开始寻找 DOS 纪元。
  • 如果底层库不支持 now(),并正确地告诉你从 epoch 到现在的 delta 而没有包装,那么它就坏了,简单。我认为实时时钟有一个“程序启动”或“处理器启动”的时期,所以使用纳秒应该是安全的。
猜你喜欢
  • 2017-08-31
  • 1970-01-01
  • 2018-02-03
  • 2016-07-20
  • 1970-01-01
  • 1970-01-01
  • 2015-02-27
  • 2012-08-21
  • 1970-01-01
相关资源
最近更新 更多