c++ 有没有一种可靠的方法可以在双打中传递微秒时间?

wsewodh2  于 2023-03-05  发布在  其他
关注(0)|答案(3)|浏览(150)

我正在尝试解决一个相当烦人的问题,我需要时间戳精度为亚毫秒的报告数据。问题是时间戳消息字段是typedefed双精度型。我只为单个接收客户端打包此消息,但此消息是标准化数据服务的一部分,我无法在短期内修改此字段。
我正在考虑将两个32位值(表示UTC秒和微秒)复制到一个double中的后果,如下所示。我并不喜欢这样,但它似乎确实有效。然而,我不明白的是,为什么当我颠倒秒和微秒时,它不起作用。如果我以另一种方式 Package 它,第二次总是正确的。但微秒时间在(+/-)1 - 500 us范围内随表面上的随机值变化

timeval tv;
gettimeofday(&tv, nullptr);
std::cout << " Original seconds: " << tv.tv_sec << "\n";
std::cout << " Original useconds: " << tv.tv_usec << "\n";
uint64_t ts = ((tv.tv_usec << 32) | tv.tv_sec);
double dts = (double)ts;
unsigned useconds = ((uint64_t)dts >> 32);
unsigned seconds = ((uint64_t)dts & 0x00000000FFFFFFFF);
std::cout << " Final seconds: " << seconds << "\n";
std::cout << " Final useconds: " << useconds << "\n";
std::cout << "diff: " << useconds - tv.tv_usec << "\n";

在看了IEEE-754之后,我更困惑了。看起来如果有任何位被错误地表示,那就是更高的位。
无论如何,我很乐意听到对这种行为的解释,以及如何可靠地完成这一任务的任何建议。

abithluo

abithluo1#

要了解为什么会发生这种情况,必须考虑ts在转换为double之前的值。

uint64_t ts = ((tv.tv_usec << 32) | tv.tv_sec);

ts的值最大为1000000·232。64位double使用53位尾数存储存储值的数字。这意味着对于小于253的值,双精度将精确到最接近的整数,因此在转换为double时不会丢失数据。对于较大的值,double只能存储53位精度,从而妨碍了一些整数的表示。
当您切换微秒和秒时,ts的值变为大约(16亿)·232,或大约262。由于在转换为double时只存储53位二进制精度数字,因此该数字四舍五入到最接近的29=512,从而导致您看到的错误。
如果我正确地理解了您要做的事情,您需要时间戳精确到微秒,那么您是否考虑过像这样以微秒为单位计算总时间戳?

uint64_t ts = tv.tv_sec * 1000000 + tv.tv_usec;

这将保证微秒级的精度,而不会接近double的253极限。

l0oc07j2

l0oc07j22#

IEEE-754双精度浮点数可以表示264个不同的值,但它的范围大约是2.210-308-1.810308。这个范围超过了264,因此显然它不能表示该范围内的每个不同值。它放弃了 precision 表示range。
一个简单的static_castdouble(这是C风格的强制转换最终要做的)不足以将uint64_t的位压缩到double中,这将把uint64_tnumeric 值转换为double,可能会损失所有的精度。
你可以做的是将uint64_t的底层字节复制到double的存储中。

double dts = std::bit_cast<double>(ts);

// or, pre-C++17
double dts;
std::copy(
    reinterpret_cast<unsigned char*>(&ts),
    reinterpret_cast<unsigned char*>(&ts) + sizeof(uint64_t),
    &dts
);

当在另一端解包值时,您可以反向执行相同的操作。

e1xvtsh3

e1xvtsh33#

我对这个问题的理解是,我们需要一个定时器,它返回的double至少能以微秒级的分辨率返回系统时间。下面是为此目的编写的代码,我已经使用了二十多年,没有出现任何问题。
以秒为单位返回系统时间,比微秒分辨率 * 目前 * 略细,微秒分辨率需要20位,从1970年1月1日至今的秒数目前约为16.67亿,因此适合31位。由于基于IEEE-754 binary64double具有52 stored significand位,这允许舒适地存储自1月1日以来的微秒的总计数,1970年的这个时候和未来几十年。
然而,如果需要将时间戳机制保存几个世纪,这种方法就 * 不 * 合适了,因为它最终会崩溃(由于double中可用于存储微秒计数的位数有限),除非Unix-y平台将系统时间保持的起点重新偏置到更晚的日期。

#if defined(_WIN32)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
double second (void)
{
    LARGE_INTEGER t;
    static double oofreq;
    static int checkedForHighResTimer;
    static BOOL hasHighResTimer;

    if (!checkedForHighResTimer) {
        hasHighResTimer = QueryPerformanceFrequency (&t);
        oofreq = 1.0 / (double)t.QuadPart;
        checkedForHighResTimer = 1;
    }
    if (hasHighResTimer) {
        QueryPerformanceCounter (&t);
        return (double)t.QuadPart * oofreq;
    } else {
        return (double)GetTickCount() * 1.0e-3;
    }
}
#elif defined(__linux__) || defined(__APPLE__)
#include <stddef.h>
#include <sys/time.h>
double second (void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (double)tv.tv_sec + (double)tv.tv_usec * 1.0e-6;
}
#else
#error unsupported platform
#endif

相关问题