微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

std::chrono::duration 与不同代表之间转换的意外错误

如何解决std::chrono::duration 与不同代表之间转换的意外错误

我注意到了意外行为,并将其范围缩小到从一个持续时间到另一个持续时间的转换。

#include <chrono>
#include <iostream>
#include <cmath>

using sec = std::chrono::seconds;
using fsec = std::chrono::duration<float>;
using dsec = std::chrono::duration<double>;

template<typename T>
auto count_intervals(T time_jump) -> uint32_t
{
  constexpr auto interval = sec(10);
  std::cout << "[debug] " << time_jump.count() << std::endl;
  std::cout << "[debug] " << interval.count() << std::endl;
  std::cout << "[debug] " << time_jump.count() / interval.count() << std::endl;
  printf("[debug] %.10f\n",time_jump.count());
  printf("[debug] %.10f\n",time_jump.count() / interval.count());
  const uint32_t k = std::ceil(time_jump.count() / interval.count());
  std::cout << "[debug] " << k << std::endl;
  return k;
}

int main()
{
  using clock_t = std::chrono::system_clock;
  using time_point_t = typename clock_t::time_point;
  using std::chrono::duration_cast;

  const auto start_point = time_point_t();
  const auto a = start_point + sec(25);
  const auto b = start_point + sec(55);

  std::cout << count_intervals<fsec>(b - a) << std::endl;
  std::cout << count_intervals<fsec>(duration_cast<fsec>(b - a)) << std::endl;
  const auto distance = duration_cast<std::chrono::duration<uint32_t>>(b - a);
  std::cout << count_intervals<fsec>(distance) << std::endl;
  std::cout << count_intervals<dsec>(b - a) << std::endl;
}

我不明白为什么 std::ceil 会四舍五入。当我使用 dsec 而不是 fsec 时,似乎一切都很好。问题是在 float 持续时间表示的情况下我的错误是什么。我以为我按预期使用 std::chrono::duration 但显然情况并非如此。我应该如何正确执行到 fsec 的转换?一般来说,我希望有一个几分之一秒的持续时间格式。

输出

[debug] 30
[debug] 10
[debug] 3
[debug] 30.0000019073
[debug] 3.0000002384
[debug] 4
4
[debug] 30
[debug] 10
[debug] 3
[debug] 30.0000019073
[debug] 3.0000002384
[debug] 4
4
[debug] 30
[debug] 10
[debug] 3
[debug] 30.0000000000
[debug] 3.0000000000
[debug] 3
3
[debug] 30
[debug] 10
[debug] 3
[debug] 30.0000000000
[debug] 3.0000000000
[debug] 3
3

解决方法

在您的平台上,system_clock::time_pointtime_point<system_clock,nanoseconds> 的类型别名。事实证明,这个细节很重要。

运行此测试的 float 版本时,第一步是将 b - a 转换为 duration<float> 以调用 count_intervalsb - a 的类型是 nanosecondsb - a 的值为 30'000'000'000ns

存储整数 30'000'000'000 需要 25 位精度。 IEEE 浮点数具有 24 位精度。

30'000'000'000ns 转换为 duration<float> 的第一步是将数字 30'000'000'000 存储到 common_typesystem_clock::repfloat 中,即 { {1}}。它只能大致做到这一点。它实际上存储了 float,这是“向最近舍入,甚至平局”策略下最好的可表示值。然后将该近似值除以 30'000'001'024(存储在 1'000'000'000 中),得到近似值 float。它得到的确切值是 300x1.e00002p+4

从现在开始,计算与精确略有偏差。

您可以通过将 30.000001... 设置为 cout 模式,这将准确地输出浮点数来亲眼看到这一点:

hexfloat

和/或,将精度设置为 15(左右):

std::cout << std::hexfloat;

计算与 std::cout.precision(15); 的预期一致,因为 duration<double> 有 53 位精度,因此可以准确地存储 double

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。