我正在编写一些必须处理 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 中以同样的方式失败。简而言之,要从 system 转换为 ntp 表示,它会乘以 8388608/1953125 的比率,并在另一个方向上乘以相反的比率。它首先乘以分子,然后除以分母。数学是正确的,但初始乘法会溢出 64 位表示并被截断,尽管实际转换(除法后)仍然很容易用这些位表示。
我可以手动进行此转换,但我的问题如下:
- 这是实现中的错误,还是只是一个限制?
- 如果有限制,我是否应该意识到这将是一个问题?我没有看到任何可能导致能够猜测这不起作用的文档。
- 是否有一种通用的方法来实现这一点,而不是同一个问题?
- 是否应该报告,向谁报告?
- 最后,如果当 C++20 出现时,我使用具有手动策划
to_sys
和from_sys
功能的 NTP 时钟,我是否能够简单地使用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 << '\n';
std::cout << "Naive conversion to ntp and back\n";
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 << '\n';
std::cout << "Broken down conversion to ntp, naive back\n";
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 << '\n';
std::cout << "Broken down conversion to std from ntp\n";
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)