使用 std::chrono 的 NTP 时间戳
我试图在 C++ 中使用 NTP 时间戳(包括 NTP 纪元)表示 <代码>std::chrono。因此,我决定使用 64 位无符号 int (unsigned long long
) 作为刻度,并将其除以最低 28 位表示秒的一小部分(接受 4 位的截断)与原始标准时间戳相比),接下来的 32 位代表纪元的秒数,最高 4 位代表纪元。这意味着每个刻度需要 1 / (2^28 - 1)
秒。
我现在有以下简单的实现:
#include <chrono>
/**
* Implements a custom C++11 clock starting at 1 Jan 1900 UTC with a tick duration of 2^(-28) seconds.
*/
class NTPClock
{
public:
static constexpr bool is_steady = false;
static constexpr unsigned int era_bits = 4; // epoch uses 4 bits
static constexpr unsigned int fractional_bits = 32-era_bits; // fraction uses 28 bits
static constexpr unsigned int seconds_bits = 32; // second uses 32 bits
using duration = std::chrono::duration<unsigned long long, std::ratio<1, (1<<fractional_bits)-1>>;
using rep = typename duration::rep;
using period = typename duration::period;
using time_point = std::chrono::time_point<NTPClock>;
/**
* Return the current time of this. Note that the implementation is based on the assumption
* that the system clock starts at 1 Jan 1970, which is not defined with C++11 but seems to be a
* standard in most compilers.
*
* @return The current time as represented by an NTP timestamp
*/
static time_point now() noexcept
{
return time_point
(
std::chrono::duration_cast<duration>(std::chrono::system_clock::now().time_since_epoch())
+ std::chrono::duration_cast<duration>(std::chrono::hours(24*25567)) // 25567 days have passed between 1 Jan 1900 and 1 Jan 1970
);
};
}
不幸的是,一个简单的测试表明这不能按预期工作:
#include <chrono>
#include <iostream>
#include <catch2/catch.hpp>
#include "NTPClock.h"
using namespace std::chrono;
TEST_CASE("NTPClock_now")
{
auto ntp_dur = NTPClock::now().time_since_epoch();
auto sys_dur = system_clock::now().time_since_epoch();
std::cout << duration_cast<hours>(ntp_dur) << std::endl;
std::cout << ntp_dur << std::endl;
std::cout << duration_cast<hours>(sys_dur) << std::endl;
std::cout << sys_dur << std::endl;
REQUIRE(duration_cast<hours>(ntp_dur)-duration_cast<hours>(sys_dur) == hours(24*25567));
}
输出:
613612h
592974797620267184[1/268435455]s
457599h
16473577714886015[1/10000000]s
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PackageTest.exe is a Catch v2.11.1 host application.
Run with -? for options
-------------------------------------------------------------------------------
NTPClock_now
-------------------------------------------------------------------------------
D:\Repos\...\TestNTPClock.cpp(10)
...............................................................................
D:\Repos\...\TestNTPClock.cpp(18): FAILED:
REQUIRE( duration_cast<hours>(ntp_dur)-duration_cast<hours>(sys_dur) == hours(24*25567) )
with expansion:
156013h == 613608h
===============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed
我还在 NTPClock::now
中删除了 25567 天的偏移量,断言相等但没有成功。我不确定这里出了什么问题。有人可以帮忙吗?
I'm trying to represent NTP timestamps (including the NTP epoch) in C++ using std::chrono
. Therefore, I decided to use a 64-bit unsigned int (unsigned long long
) for the ticks and divide it such that the lowest 28-bit represent the fraction of a second (accepting trunction of 4 bits in comparison to the original standard timestamps), the next 32-bit represent the seconds of an epoch and the highest 4-bit represent the epoch. This means that every tick takes 1 / (2^28 - 1)
seconds.
I now have the following simple implementation:
#include <chrono>
/**
* Implements a custom C++11 clock starting at 1 Jan 1900 UTC with a tick duration of 2^(-28) seconds.
*/
class NTPClock
{
public:
static constexpr bool is_steady = false;
static constexpr unsigned int era_bits = 4; // epoch uses 4 bits
static constexpr unsigned int fractional_bits = 32-era_bits; // fraction uses 28 bits
static constexpr unsigned int seconds_bits = 32; // second uses 32 bits
using duration = std::chrono::duration<unsigned long long, std::ratio<1, (1<<fractional_bits)-1>>;
using rep = typename duration::rep;
using period = typename duration::period;
using time_point = std::chrono::time_point<NTPClock>;
/**
* Return the current time of this. Note that the implementation is based on the assumption
* that the system clock starts at 1 Jan 1970, which is not defined with C++11 but seems to be a
* standard in most compilers.
*
* @return The current time as represented by an NTP timestamp
*/
static time_point now() noexcept
{
return time_point
(
std::chrono::duration_cast<duration>(std::chrono::system_clock::now().time_since_epoch())
+ std::chrono::duration_cast<duration>(std::chrono::hours(24*25567)) // 25567 days have passed between 1 Jan 1900 and 1 Jan 1970
);
};
}
Unfortunately, a simple test reveals this does not work as expected:
#include <chrono>
#include <iostream>
#include <catch2/catch.hpp>
#include "NTPClock.h"
using namespace std::chrono;
TEST_CASE("NTPClock_now")
{
auto ntp_dur = NTPClock::now().time_since_epoch();
auto sys_dur = system_clock::now().time_since_epoch();
std::cout << duration_cast<hours>(ntp_dur) << std::endl;
std::cout << ntp_dur << std::endl;
std::cout << duration_cast<hours>(sys_dur) << std::endl;
std::cout << sys_dur << std::endl;
REQUIRE(duration_cast<hours>(ntp_dur)-duration_cast<hours>(sys_dur) == hours(24*25567));
}
Output:
613612h
592974797620267184[1/268435455]s
457599h
16473577714886015[1/10000000]s
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PackageTest.exe is a Catch v2.11.1 host application.
Run with -? for options
-------------------------------------------------------------------------------
NTPClock_now
-------------------------------------------------------------------------------
D:\Repos\...\TestNTPClock.cpp(10)
...............................................................................
D:\Repos\...\TestNTPClock.cpp(18): FAILED:
REQUIRE( duration_cast<hours>(ntp_dur)-duration_cast<hours>(sys_dur) == hours(24*25567) )
with expansion:
156013h == 613608h
===============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed
I also removed the offset of 25567 days in NTPClock::now
asserting equality without success. I'm not sure what is going wrong here. Can anybody help?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
遗憾的是,您的刻度周期:1/268'435'455 非常好,而且在使用您所需的转化次数时也无法大幅减少分数(即在
system_clock::duration
和NTPClock::duration
之间,这会导致unsigned long long
的内部溢出。NTPClock::rep
例如,在 Windows 上,
system_clock
时钟周期为 1/10,000,000 秒。now()
的值约为 1.6 x 1016 要将其转换为NTPClock::duration
,您必须进行计算。 1.6 x 1016 乘以 53,687,091/2,000,000 第一步是该值乘以转换系数的分子,约为 8 x 1023,它溢出了unsigned long long
有几种方法可以克服这种溢出,并且两者都涉及至少使用具有更大范围的中间表示。人们可以使用 128 位整数类型,但我不认为它可以在 Windows 上使用,除非是通过第三方库。
long double
是另一种选择。这可能看起来像:也就是说,执行偏移移位而不进行转换(duration_cast 最终除了将
system_clock::duration
单位),然后将其转换为具有imd
的中间表示形式>long doublerep
,以及与NTPClock
相同的period
。这将使用long double
计算 1.6 x 1016 乘以 53,687,091/2,000,000。然后最后duration_cast
到NTPClock::duration
。最终的long double
转换为unsigned long long
之外什么也不做,因为转换因子只是 1/1。完成同样事情的另一种方法是:
这利用了这样一个事实,即您可以将任何
duration
乘以1
,但使用备用单位,结果将具有>rep
与两个参数的common_type
,但在其他方面具有相同的值。即std::chrono::hours(24*25567)*1.0L
是一个基于long double
的小时
。long double
执行其余的计算,直到duration_cast
将其返回到NTPClock::duration
。第二种方式编写起来更简单,但代码审查者可能不理解
*1.0L
的意义,至少在它成为更常见的习惯用法之前是这样。Your tick period: 1/268'435'455 is unfortunately both extremely fine and also doesn't lend itself to much of a reduced fraction when your desired conversions are used (i.e. between
system_clock::duration
andNTPClock::duration
. This is leading to internal overflow of yourunsigned long long
NTPClock::rep
.For example, on Windows the
system_clock
tick period is 1/10,000,000 seconds. The current value ofnow()
is around 1.6 x 1016. To convert this toNTPClock::duration
you have to compute 1.6 x 1016 times 53,687,091/2,000,000. The first step in that is the value times the numerator of the conversion factor which is about 8 x 1023, which overflowsunsigned long long
.There's a couple of ways to overcome this overflow, and both involve using at least an intermediate representation with a larger range. One could use a 128 bit integral type, but I don't believe that is available on Windows, except perhaps by a 3rd party library.
long double
is another option. This might look like:That is, perform the offset shift with no conversion (
system_clock::duration
units), then convert that to the intermediate representationimd
which has along double
rep
, and the sameperiod
asNTPClock
. This will uselong double
to compute 1.6 x 1016 times 53,687,091/2,000,000. Then finallyduration_cast
that toNTPClock::duration
. This finalduration_cast
will end up doing nothing but castinglong double
tounsigned long long
as the conversion factor is simply 1/1.Another way to accomplish the same thing is:
This takes advantage of the fact that you can multiply any
duration
by1
, but with alternate units and the result will have arep
with thecommon_type
of the two arguments, but otherwise have the same value. I.e.std::chrono::hours(24*25567)*1.0L
is along double
-basedhours
. And thatlong double
carries through the rest of the computation until theduration_cast
brings it back toNTPClock::duration
.This second way is simpler to write, but code reviewers may not understand the significance of the
*1.0L
, at least until it becomes a more common idiom.