可以将一个static_cast <float>从double赋值给double进行优化吗?

Ben*_*Ben 22 c++ floating-point casting

我偶然发现了一个我认为不必要的功能,并且通常让我害怕:

float coerceToFloat(double x) {
    volatile float y = static_cast<float>(x);
    return y;
}
Run Code Online (Sandbox Code Playgroud)

然后使用如下:

// double x
double y = coerceToFloat(x);
Run Code Online (Sandbox Code Playgroud)

这与这样做有什么不同吗?:

double y = static_cast<float>(x);
Run Code Online (Sandbox Code Playgroud)

目的似乎是将双倍降低到单精度.它闻起来像极端偏执狂写的东西.

Eri*_*hil 12

static_cast<float>(x)需要消除任何多余的精度,产生一个float.虽然C++标准通常允许实现在表达式中保留过多的浮点精度,但必须通过强制转换和赋值运算符来消除该精度.

使用更高精度的许可证在C++草案N4659第8条第13段中:

浮动操作数的值和浮动表达式的结果可以以比该类型所需的精度和范围更高的精度和范围来表示; 因此不改变类型.64

脚注64说:

转换和赋值运算符仍必须按8.4,8.2.9和8.18中的描述执行其特定转换.

  • 我不确定这是否正确.脚注是非规范性的.也许这个脚注的意图是说使用演员/作业,可以改变类型.但过剩的精度可以保持.这是一个实施质量问题,因此编译器可能会降低额外的精度,但他们不需要这样做.我不是说这是正确的解释,但对我来说似乎是合情合理的. (5认同)

Pet*_*ker 11

跟进@NathanOliver的评论 - 允许编译器以比操作数所需类型更高的精度进行浮点数学运算.通常在x86上,这意味着它们将所有内容都作为80位值执行,因为这是硬件中最有效的.只有在存储一个值时,才必须将其恢复为该类型的实际精度.即使这样,默认情况下大多数编译器都会执行违反此规则的优化,因为强制更改精度会降低浮点运算的速度.大部分时间都没关系,因为额外的精确度是无害的.如果您是一个坚持者,您可以使用命令行开关强制编译器遵守该存储规则,您可能会发现浮点计算速度明显变慢.

在该函数中,标记变量volatile告诉编译器它不能忽略存储该值; 反过来,这意味着它必须降低传入值的精度以匹配它所存储的类型.所以希望这会强制截断.

并且,不,编写演员而不是调用该函数是不一样的,因为编译器(在其不符合模式下)可以跳过赋值,y如果它确定它可以生成更好的代码而不存储值,它可以也可以跳过截断.请记住,目标是尽可能快地运行浮点计算,并且必须处理有关降低中间值精度的琐碎规则只会减慢速度.

在大多数情况下,通过跳过中间截断来运行平稳是严重的浮点应用程序所需要的.需要截断存储的规则更多是希望而不是现实要求.

另外,Java最初要求所有浮点数学都以所涉及类型所需的精确精度完成.您可以通过告诉它不要将fp类型扩展到80位来在英特尔硬件上执行此操作.这遭到了来自数字打交道大声抱怨,因为这使得计算慢.Java很快改为"严格"fp和"非严格"fp的概念,严重的数字运算使用非严格,即使其与硬件支持一样快.彻底理解浮点数学(包括我)的人想要速度,并且知道如何应对导致的精度差异.


Yak*_*ont 8

一些编译器具有"扩展精度"的概念,其中双精度带有超过64位的数据.这导致浮点计算与IEEE标准不匹配.

上面的代码可能是为了防止编译器上的扩展精度标志消除精度损失.这些标志明确违反了双精度和浮点值的精确假设.似乎有理由认为他们不会对volatile变量这样做.


har*_*old 6

无论是否允许这样的强制转换被优化掉,它确实会发生并且易失性分配会阻止它发生.

例如,MSVC编译为32位(因此使用x87)/Ox /fp:fast:

_x$ = 8                                       ; size = 8
float uselessCast(double) PROC                         ; uselessCast
        fld     QWORD PTR _x$[esp-4]
        ret     0
float uselessCast(double) ENDP                         ; uselessCast

_y$ = 8                                       ; size = 4
_x$ = 8                                       ; size = 8
float coerceToFloat(double) PROC                   ; coerceToFloat
        fld     QWORD PTR _x$[esp-4]
        fstp    DWORD PTR _y$[esp-4]
        fld     DWORD PTR _y$[esp-4]
        ret     0
float coerceToFloat(double) ENDP 
Run Code Online (Sandbox Code Playgroud)

哪里uselessCast是如下,并coerceToFloat在问题.

float uselessCast(double x)
{
    return static_cast<float>(x);
}
Run Code Online (Sandbox Code Playgroud)

同样,GCC和Clang同 -O3 -ffast-math -m32 -mfpmath=387

uselessCast(double):
    fld     QWORD PTR [esp+4]
    ret
coerceToFloat(double):
    sub     esp, 20
    fld     QWORD PTR [esp+24]
    fstp    DWORD PTR [esp+12]
    fld     DWORD PTR [esp+12]
    add     esp, 20
    ret
Run Code Online (Sandbox Code Playgroud)

以上所有的Godbolt链接

当然你可能会争辩说,/fp:fast或者-ffast-math你不应该期待浮点运算的任何东西,但你可能需要它,但仍然能够丢弃多余的精度.

  • 我可以看到允许程序员在将值存储到注册限定的自动对象时允许编译器放弃截断/舍入的有用性.这对于小符号整数类型以及浮点类型都是如此(因此在`register int8_t foo = 127; foo = foo + 1;`之后,`foo`的值可以存储+128或-128 at编译器的休闲).请注意,这样的赋值*不是*UB.然而,程序员使用这种弃权的能力会因为编译器也将它们应用于其唯一目的可能是强制截断的演员表而受到破坏. (2认同)