使用 std::chrono 的 NTP 时间戳
NTP timestamps using std::chrono
我正在尝试使用 std::chrono
在 C++ 中表示 NTP timestamps(包括 NTP 纪元)。因此,我决定对刻度使用 64 位无符号整数 (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 天的偏移量,断言平等但没有成功。我不确定这里出了什么问题。有人可以帮忙吗?
您的报价周期: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
是另一种选择。这可能看起来像:
static time_point now() noexcept
{
using imd = std::chrono::duration<long double, period>;
return time_point
(
std::chrono::duration_cast<duration>(imd{std::chrono::system_clock::now().time_since_epoch()
+ std::chrono::hours(24*25567)})
);
};
也就是说,在没有转换的情况下执行偏移量移位(system_clock::duration
单位),然后将其转换为具有 long double
rep
的中间表示 imd
, period
与 NTPClock
相同。这将使用 long double
计算 1.6 x 1016 次 53,687,091/2,000,000。然后最后 duration_cast
到 NTPClock::duration
。最后的 duration_cast
除了将 long double
转换为 unsigned long long
之外什么都不做,因为转换因子只是 1/1.
完成同样事情的另一种方法是:
static time_point now() noexcept
{
return time_point
(
std::chrono::duration_cast<duration>(std::chrono::system_clock::now().time_since_epoch()
+ std::chrono::hours(24*25567)*1.0L)
);
};
这利用了这样一个事实,即您可以将任何 duration
乘以 1
,但使用交替单位,结果将具有 rep
和 common_type
的两个参数,但在其他方面具有相同的值。 IE。 std::chrono::hours(24*25567)*1.0L
是基于 long double
的 hours
。 long double
进行剩余的计算,直到 duration_cast
将其带回 NTPClock::duration
。
第二种方式写起来更简单,但代码审查者可能不理解 *1.0L
的重要性,至少在它成为更常见的习惯用法之前是这样。
我正在尝试使用 std::chrono
在 C++ 中表示 NTP timestamps(包括 NTP 纪元)。因此,我决定对刻度使用 64 位无符号整数 (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 天的偏移量,断言平等但没有成功。我不确定这里出了什么问题。有人可以帮忙吗?
您的报价周期: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
是另一种选择。这可能看起来像:
static time_point now() noexcept
{
using imd = std::chrono::duration<long double, period>;
return time_point
(
std::chrono::duration_cast<duration>(imd{std::chrono::system_clock::now().time_since_epoch()
+ std::chrono::hours(24*25567)})
);
};
也就是说,在没有转换的情况下执行偏移量移位(system_clock::duration
单位),然后将其转换为具有 long double
rep
的中间表示 imd
, period
与 NTPClock
相同。这将使用 long double
计算 1.6 x 1016 次 53,687,091/2,000,000。然后最后 duration_cast
到 NTPClock::duration
。最后的 duration_cast
除了将 long double
转换为 unsigned long long
之外什么都不做,因为转换因子只是 1/1.
完成同样事情的另一种方法是:
static time_point now() noexcept
{
return time_point
(
std::chrono::duration_cast<duration>(std::chrono::system_clock::now().time_since_epoch()
+ std::chrono::hours(24*25567)*1.0L)
);
};
这利用了这样一个事实,即您可以将任何 duration
乘以 1
,但使用交替单位,结果将具有 rep
和 common_type
的两个参数,但在其他方面具有相同的值。 IE。 std::chrono::hours(24*25567)*1.0L
是基于 long double
的 hours
。 long double
进行剩余的计算,直到 duration_cast
将其带回 NTPClock::duration
。
第二种方式写起来更简单,但代码审查者可能不理解 *1.0L
的重要性,至少在它成为更常见的习惯用法之前是这样。