是否可以优化从double分配为double的static_cast <float>?

时间:2018-11-02 12:51:35

标签: c++ casting floating-point

我偶然发现了一个我认为不必要的功能,这通常使我感到恐惧:

float coerceToFloat(double x) {
    volatile float y = static_cast<float>(x);
    return y;
}

然后这样使用:

// double x
double y = coerceToFloat(x);

与做这件事有什么不同吗?:

double y = static_cast<float>(x);

目的似乎是将双精度分解为单精度。闻起来像是出于极端的妄想症。

4 个答案:

答案 0 :(得分:12)

需要

static_cast<float>(x)来消除所有多余的精度,从而产生float。尽管C ++标准通常允许实现在表达式中保留多余的浮点精度,但是必须由强制转换和赋值运算符删除该精度。

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

  

浮动操作数的值和浮动表达式的结果可以更大的形式表示   精度和范围超出类型要求;类型不会因此更改。 64

脚注64说:

  

强制转换和赋值运算符仍必须按照8.4、8.2.9和8.18中所述执行其特定的转换。

答案 1 :(得分:11)

紧跟@NathanOliver的评论,允许编译器以比操作数类型更高的精度进行浮点数学运算。通常在x86上,这意味着它们将所有操作都作为80位值执行,因为这是硬件中最高效的。只有在存储值时才必须将其还原为类型的实际精度。即使这样,大多数编译器默认仍会进行违反该规则的优化,因为强制精度更改会减慢浮点运算的速度。在大多数情况下都可以,因为额外的精度无害。如果您是坚持不懈的人,则可以使用命令行开关强制编译器遵守该存储规则,并且您可能会发现浮点计算的速度明显降低。

在该函数中,标记变量volatile告诉编译器它不能逃避存储该值。反过来,这意味着它必须降低传入值的精度以匹配其存储在其中的类型。因此希望这将强制截断。

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

在大多数情况下,严重的浮点应用程序需要通过跳过中间的截断来实现平坦运行。要求对存储进行截断的规则比现实的要求更有希望。

附带说明,Java最初要求所有浮点数学运算必须以所涉及的类型所需的精确度进行。您可以通过告诉英特尔不要将fp类型扩展到80位来实现。数字处理者大声抱怨,因为这使得计算很多变慢了。 Java很快改变为“严格” fp和“非严格” fp的概念,严重的数字运算使用了非严格的,即使其与硬件支持一样快。完全了解浮点数学(不包括我在内)的人想要速度,并且知道如何应对结果的精度差异。

答案 2 :(得分:8)

某些编译器具有“扩展精度”的概念,其中double携带超过64位的数据。这导致浮点计算与IEEE标准不匹配。

以上代码可能是为了防止编译器上的扩展精度标志消除精度损失。此类标志明显违反了double和浮点值的精度假设。似乎他们不会对volatile变量这样做。

答案 3 :(得分:6)

无论是否允许优化此类转换,它都会发生,而可变分配会阻止其发生。

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

_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 

uselessCast如下,coerceToFloat如下。

float uselessCast(double x)
{
    return static_cast<float>(x);
}

类似地,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

Godbolt link for all the above

当然,您可能会争辩说,对于/fp:fast-ffast-math,无论如何您都不应该对浮点算术有所期望,但是您可能会需要它,但仍然可以舍弃多余的精度。