将浮点值舍入为例如单精度

Round floating-point value to e.g. single precision

C 和 C++ 提供多种宽度的浮点数据类型,但未指定精度。编译器可以自由使用理想化算术来简化表达式,在计算 float 值的表达式时使用双精度,或者使用双精度寄存器来保存 float 变量或公共变量的值子表达式。

错了请指正错了,见编辑,但是把内存中的float提升到双精度寄存器中也是合法的,所以存储一个值然后将其加载回不一定会截断位。

将数字转换为较低精度的最安全、最便携的方法是什么?理想情况下,它也应该是高效的,在 SSE2 上编译为 cvtsd2ss。 (因此,虽然 volatile 可能是一个答案,但我更喜欢更好的答案。)

编辑: 总结一些评论和发现…

但是,某些编译器(尤其是 x86-32 上的 GCC)会非法忘记某些精度转换。

编辑 2: 有些人对未能缩小中间结果的一致性表示怀疑。

请注意,允许恒等式 x - x = 0a + b - b + c 简化为 a + c 与使加法可交换或结合不同。 a + b + c 仍然与 a + c + ba + (b + c) 不同,因为 CPU 仅提供两个加数和一个舍入结果的加法。

我不太确定我是否在这里分享你的恐惧......我尝试了这个美化的 cast-as-a-function:

float to_float(double x)
{
  return (float) x;
}

当输入 Compiler Explorer 时,我得到:

to_float(double):
        push     rbp
        mov      rbp, rsp
        movsd    QWORD PTR [rbp-8], xmm0
        cvtsd2ss xmm0, QWORD PTR [rbp-8]
        pop      rbp
        ret

这似乎立即生成了请求的操作码 (cvtsd2ss),我什至没有输入任何编译器选项来强制 SSE2 或任何东西。

我会说强制转换必须转换为目标类型,据我所知,编译器不能随意忽略强制转换。

您能否提供一些您认为编译器可以忽略强制转换的案例?也许代码中潜伏着某种未定义的行为,这使得编译器采取了意想不到的捷径。

C99 5.2.4.2.2p8 明确表示

assignment and cast [..] remove all extra range and precision

因此,如果您想将范围和精度限制为浮点数,只需转换为 float,或分配给 float 变量。

您甚至可以做类似 (double)((float)d) 的事情(使用额外的括号以确保人们正确阅读它),将变量 d 限制为 float 精度和范围,然后转换它回到 double。 (即使 ddouble,标准 C 编译器也不允许对其进行优化;它必须将精度和范围限制为 float。)

我已经在实际实现中使用了它,例如Kahan summation algorithm,它可用于允许 C 编译器进行非常积极的优化,但没有失效的风险。