是否有一种定义明确且高性能的方式将双精度位转换为 uint64_t 并返回

Posted

技术标签:

【中文标题】是否有一种定义明确且高性能的方式将双精度位转换为 uint64_t 并返回【英文标题】:Is there a well-defined and performant way to bitwise convert double to uint64_t and back 【发布时间】:2021-10-27 10:10:56 【问题描述】:

以前没有回答我的问题的类似问题:

bit cast uint64_t to double and back in a macro

Converting uint64_t to Double Value

我需要将double 保存到 STM32L476 微控制器的闪存中。闪存控制器以 64 位块工作,ST 的 HAL API 采用uint64_t 参数来写入数据。为此,我需要将 double 变量中的位转换为一个或多个uint64_ts。

我尝试了以下,是UB:

uint64_t flash_write_val = *(uint64_t*)&double_value;

但是,我收到有关违反严格别名规则的编译器警告。如果不调用 UB,我将如何做到这一点?它不需要超级便携。它只能在 Cortex M4F 内核上有效。

我正在考虑这个:

union uint64_double 
    uint64_t flash_friendly;
    double math_friendly;
;

这是一个好方法,还是我仍然在踢自己的脚?

【问题讨论】:

在 C 中使用联合进行类型双关是可以的,但在 C++ 中不是:Unions and type-punning。 您链接的两个帖子没有回答您的问题的原因是什么? 是否需要数据为uint64_t 或者您可以使用例如uint8_t buffer[8]?您需要对数据进行特定对齐吗? @Lundin 没那么奇怪; STM32F476 片上闪存在内存和高速缓存/加速器之间有一个 64 位数据路径(实际上是 72 个 - 包括 8 位 ECC),并且只能以 64 位对齐的“双字”进行编程。好吧,也许这很奇怪,但并非莫名其妙,在这种情况下不是 HAL 设计问题。我想这是硬件优化,通过使其不那么灵活从而需要更少的逻辑(因此需要更少的内存空间)来尽可能多地填充闪存。 STM32 上的 flash 实现因系列而异,一个部分的 flash 代码不一定适用于另一个! @LouisCloete 那是非常糟糕的编程......我不会使用 ST HAL。 【参考方案1】:

只需使用memcpy 将字节复制到您想要的位置。

memcpy(&flash_write_val, &double_val, sizeof(double_val));

【讨论】:

这是正确答案。虽然我可能会建议在某处添加static_assert(sizeof(double)==sizeof(uint64_t), "oops"); 以用于防御目的。 这是C++的正确答案。对于 C(用它标记问题),联合很好。 我还是选择了 memcpy,因为我意识到与保存时的字符串处理相比,开销可以忽略不计(我正在使用现有的 SCPI 解析基础架构来保存值)。将以前的 double 的每次出现都更改为新的联合类型也不值得。很高兴知道在 C 中在联合中使用双关语是有效的。【参考方案2】:

只有Cortex-M3+答案!!

    指针双关语。
uint64_t flash_write_val = *(uint64_t*)&double_value;

由于 Cortex-M3 和更新版本支持非对齐访问,上述内容是 100% 安全的。我个人更喜欢memcpy的方式,但是在FLASH写函数中我通常使用指针双关。

    联合双关语 - 安全便携:
union uint64_double 
    uint64_t flash_friendly;
    double math_friendly;
;
    memcpy 函数 现代编译器最可移植,也可能是最有效的方法非常了解 memcpy 的作用,并且在许多情况下内联它。
uint64_t bar(uint64_t *);

uint64_t foo(double double_val)

    uint64_t flash_write_val;
    memcpy(&flash_write_val, &double_val, sizeof(flash_write_val));
    return bar(&flash_write_val);

foo:
        push    lr
        sub     sp, sp, #12
        mov     r2, r0
        mov     r3, r1
        mov     r0, sp
        strd    r2, [sp]
        bl      bar
        add     sp, sp, #12
        ldr     pc, [sp], #4

https://godbolt.org/z/Ws6Yr15x8

【讨论】:

我没有投反对票,但 1) 是未定义的行为。其他两个都很好。 Cortex-m 仅支持字和半字的非对齐访问。不适用于双字。我已经发生了编译器优化因为这个原因在一个双字副本中优化两个字副本的代码。因此,为了使您的代码具有可移植性和健壮性,恕我直言,如果您没有任何正当理由启用它,您应该禁用非发布版本中的未对齐支持。 Unions 和 memcpy 是可行的方法。

以上是关于是否有一种定义明确且高性能的方式将双精度位转换为 uint64_t 并返回的主要内容,如果未能解决你的问题,请参考以下文章

matlab - 将双 -8.451147490804935e+03 转换为 -8.451

如何将双精度值格式化为点后两位小数且分隔符为千位的字符串?

将双精度转换为字符串

可可 - 将双精度转换为字符串

将双精度转换为字符数组 C++

快速将双精度转换为 Int64