chrono::duration_cast problems during ratio multiplication(比率乘法过程中的Timo::DATION_CAST问题)
问题描述
我正在编写一些必须处理NTP时间的代码。我决定用std::chrono::duration
来表示它们,我希望这会让我的时间数学更容易做。
NTP时间由无符号64位整数表示,从纪元开始的秒数在高32位,小数秒在低32位。划时代的日期是1900年1月1日,但这是一个与我在这里处理的问题不同的问题,我已经知道如何处理它。对于我正在使用的示例,假设纪元为1970年1月1日,以使计算更简单。
持续时间表示很简单:std::chrono::duration<std::uint64_t, std::ratio<1, INTMAX_C(0x100000000)>>
。问题出在我试图在NTP时间和系统时钟之间进行转换时。
在我的系统上,std::chrono::system_clock::duration
是std::chrono::duration<int64_t, std::nano>
。当我尝试使用std::chrono::duration_cast
在这两个持续时间之间进行转换时,我在两个方向上都得到了大量截断。在调试器中跟踪它,我发现它与duration_cast
实现有关,它在libstdc++、libc++和Boost::chrono中以同样的方式失败。简而言之,为了从系统转换为NTP表示,它将乘以比率8388608/1953125,并在另一个方向上乘以相反的比率。它首先乘以分子,然后除以分母。数学计算是正确的,但初始乘法溢出64位表示并被截断,尽管实际转换(除法后)仍然可以很容易地用这些位表示。
我可以手动执行此转换,但我的问题如下:
- 这是实现中的错误,还是只是限制?
- 如果是限制,我是否应该意识到这将是一个问题?我没有看到任何似乎会导致能够猜测这不会起作用的文档。
- 有没有一种通用的方法来实现这一点,而不受同一问题的困扰?
- 是否应报告此情况,以及报告给谁?
- 最后,如果当C++20出现时,我使用的NTP时钟具有手动管理的
to_sys
和from_sys
函数,我是否就可以简单地使用std::chrono::clock_cast
,而不必担心其他微妙的问题?
以下是我用来测试它的代码和一些示例输出:
// #define BOOST
#ifdef BOOST
# include <boost/chrono.hpp>
# define PREFIX boost
#else
# include <chrono>
# define PREFIX std
#endif
#include <cstdint>
#include <iostream>
namespace chrono = PREFIX::chrono;
using PREFIX::ratio;
using PREFIX::nano;
using ntp_dur = chrono::duration<uint64_t, ratio<1, INTMAX_C(0x100000000)>>;
using std_dur = chrono::duration<int64_t, nano>;
int main() {
auto write = [](auto & label, auto & dur) {
std::cout << label << ": "
<< std::dec << dur.count()
<< std::hex << " (" << dur.count() << ")" << std::endl;
};
auto now = chrono::system_clock::now().time_since_epoch();
write("now", now);
std::cout << '
';
std::cout << "Naive conversion to ntp and back
";
auto a = chrono::duration_cast<std_dur>(now);
write("std", a);
auto b = chrono::duration_cast<ntp_dur>(a);
write("std -> ntp", b);
auto c = chrono::duration_cast<std_dur>(b);
write("ntp -> std", c);
std::cout << '
';
std::cout << "Broken down conversion to ntp, naive back
";
write("std", a);
auto d = chrono::duration_cast<chrono::seconds>(a);
write("std -> sec", d);
auto e = chrono::duration_cast<ntp_dur>(d);
write("sec -> ntp sec", e);
auto f = a - d;
write("std -> std frac", f);
auto g = chrono::duration_cast<ntp_dur>(f);
write("std frac -> ntp frac", f);
auto h = e + g;
write("ntp sec + ntp frac-> ntp", h);
auto i = chrono::duration_cast<std_dur>(h);
write("ntp -> std", i);
std::cout << '
';
std::cout << "Broken down conversion to std from ntp
";
write("ntp", h);
auto j = chrono::duration_cast<chrono::seconds>(h);
write("ntp -> sec", j);
auto k = chrono::duration_cast<std_dur>(j);
write("src -> std sec", j);
auto l = h - j;
write("ntp -> ntp frac", l);
auto m = chrono::duration_cast<std_dur>(l);
write("ntp frac -> std frac", m);
auto n = k + m;
write("std sec + std frac-> std", n);
}
示例输出:
now: 1530980834103467738 (153f22f506ab1eda)
Naive conversion to ntp and back
std: 1530980834103467738 (153f22f506ab1eda)
std -> ntp: 4519932809765 (41c60fd5225)
ntp -> std: 1052378865369 (f506ab1ed9)
Broken down conversion to ntp, naive back
std: 1530980834103467738 (153f22f506ab1eda)
std -> sec: 1530980834 (5b40e9e2)
sec -> ntp sec: 6575512612832804864 (5b40e9e200000000)
std -> std frac: 103467738 (62acada)
std frac -> ntp frac: 103467738 (62acada)
ntp sec + ntp frac-> ntp: 6575512613277195414 (5b40e9e21a7cdc96)
ntp -> std: 1052378865369 (f506ab1ed9)
Broken down conversion to std from ntp
ntp: 6575512613277195414 (5b40e9e21a7cdc96)
ntp -> sec: 1530980834 (5b40e9e2)
src -> std sec: 1530980834 (5b40e9e2)
ntp -> ntp frac: 444390550 (1a7cdc96)
ntp frac -> std frac: 103467737 (62acad9)
std sec + std frac-> std: 1530980834103467737 (153f22f506ab1ed9)
推荐答案
- 这是实现中的错误,还是只是限制?
只是一个限制。实施工作符合规范。
- 如果是限制,我是否应该意识到这将是一个问题?我没有看到任何似乎会导致能够猜测这不会起作用的文档。
该规范在C++标准的23.17.5.7 [time.duration.cast]中。它记录了转换算法,使其按照您的问题中所描述的那样运行。
- 有没有一种通用的方法来实现这一点,而不受同一问题的困扰?
在处理非常精细的单位或非常大的范围时,您需要注意可能出现溢出错误。chrono::duration_cast
被设计为尽可能高效地处理最常见的转换的最底层工具。chrono::duration_cast
尽可能地准确,尽可能地完全消除分歧。
duration_cast
的基础上,旨在指导存在截断的方向:
floor // truncate towards negative infinity
ceil // truncate towards positive infinity
round // truncate towards nearest, to even on tie
您可以编写自己的通用转换函数来处理困难的情况,如您所描述的情况。这种由客户提供的转换不太可能适合一般用途。例如:
template <class DOut, class Rep, class Period>
DOut
via_double(std::chrono::duration<Rep, Period> d)
{
using namespace std::chrono;
using dsec = duration<long double>;
return duration_cast<DOut>(dsec{d});
}
上述示例客户端提供的转换从duration<long double>
反弹。这不太容易溢出(尽管也不能幸免),通常在计算上会更昂贵,并且会受到精度问题的影响(取决于numeric_limits<long double>::digits
)。
对于我(numeric_limits<long double>::digits == 64
),输入1530996658751420125ns
往返到1530996658751420124ns
(相差1 ns)。该算法可以通过使用round
来改进duration_cast
(同样是以更多的计算为代价):
template <class DOut, class Rep, class Period>
DOut
via_double(std::chrono::duration<Rep, Period> d)
{
using namespace std::chrono;
using dsec = duration<long double>;
return round<DOut>(dsec{d});
}
现在我的往返行程非常适合输入1530996658751420125ns
。但是,如果您的long double
只有53位精度,那么即使round
也不能提供完美的往返。
- 是否应报告此情况,以及报告给谁?
任何人都可以通过遵循instructions at this link来针对C++标准的一半库提交缺陷报告。这样的报告将由C++标准委员会的LWG小组委员会研究。它可以被采取行动,也可以被宣布为NAD(不是缺陷)。如果问题包含建议的措辞(有关如何更改规范的详细说明),则成功的机会更高。
即,您认为标准应该怎么说?
- 最后,如果当C++20出现时,我使用的NTP时钟具有手动管理的
to_sys
和from_sys
函数,我是否就可以简单地使用std::chrono::clock_cast
,而不必担心其他微妙的问题?
找出答案的一种方法是试验<chrono>
扩展的C++20规范草案的this existing prototype。
这篇关于比率乘法过程中的Timo::DATION_CAST问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:比率乘法过程中的Timo::DATION_CAST问题
基础教程推荐
- 什么是T&&(双与号)在 C++11 中是什么意思? 2022-11-04
- C++,'if' 表达式中的变量声明 2021-01-01
- 如何在 C++ 中处理或避免堆栈溢出 2022-01-01
- 如何定义双括号/双迭代器运算符,类似于向量的向量? 2022-01-01
- 调用std::Package_TASK::Get_Future()时可能出现争用情况 2022-12-17
- 设计字符串本地化的最佳方法 2022-01-01
- 运算符重载的基本规则和习语是什么? 2022-10-31
- C++ 程序在执行 std::string 分配时总是崩溃 2022-01-01
- 您如何将 CreateThread 用于属于类成员的函数? 2021-01-01
- C++ 标准:取消引用 NULL 指针以获取引用? 2021-01-01