从 double 转换总是返回零

Posted

技术标签:

【中文标题】从 double 转换总是返回零【英文标题】:Casting from double always returns zero 【发布时间】:2020-03-31 13:27:14 【问题描述】:

为什么下面的代码用clang编译时返回零?

#include <stdint.h>
uint64_t square() 
    return ((uint32_t)((double)4294967296.0)) - 10;

它产生的程序集是:

    push    rbp
    mov     rbp, rsp
    xor     eax, eax
    pop     rbp
    ret

我本来希望 double 会变成零(整数),而减号会环绕它。换句话说,为什么要减去什么数字并不重要,因为它总是产生零?请注意,gcc 确实会产生不同的数字,正如预期的那样:

    push    rbp
    mov     rbp, rsp
    mov     eax, 4294967285
    pop     rbp
    ret

我认为将 4294967296.0 转换为 uint32_t 是未定义的行为,但即便如此,我仍希望针对不同的减数产生不同的结果。

【问题讨论】:

is undefined behaviour but even then, I would expect - 不。如果某些事情是未定义的行为,你不会想到。你不能指望。它几乎是按字面意思定义的——如果某事是未定义的行为,你不能指望代码做任何事情。 顺便说一句,(double) “cast” 在这里什么都不做,因为字面量已经是 double 类型了。 由于未定义的行为是未定义的,如果系统每次遇到未定义的行为时表现不同,那就太好了。这就是DS9k 应该发生的事情——当然,除了你不能依赖于不同的行为:如果你试图利用它,DS9k 会破坏你的代码。 请指定您想要 C 答案还是 C++ 答案。尤其是在强制转换和未定义行为方面,这些语言差异很大,通常不应被视为相同。 【参考方案1】:

将超出范围的double 转换为unsigned 类型的行为确实未定义

即使是unsigned 类型,也适用环绕式。

这是一个经常被遗忘的规则。

一旦程序控制到达一个未定义的结构,整个程序就是未定义的,甚至有些自相矛盾的是,已经运行的语句。

参考:https://timsong-cpp.github.io/cppwp/conv.fpint#1

【讨论】:

可能要添加来源:timsong-cpp.github.io/cppwp/conv.fpint#1【参考方案2】:

g++ 5.4.0 给出 4294967285(-11 无符号),因此它要由编译器来处理未定义的行为。

__Z6squarev:
LFB1023:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        movl    $-11, %eax
        movl    $0, %edx
        popl    %ebp
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc

【讨论】:

以上是关于从 double 转换总是返回零的主要内容,如果未能解决你的问题,请参考以下文章

std::string 浮动或加倍

将 OptionalDouble 转换为可选 <java.lang.Double>

返回没有尾随零的双精度类型

iOS日期转换器返回正确的日期[重复]

将 double 转换为 Int,向下舍入

转换为二进制并保留前导零