为什么编译器不能优化浮点数与0的加法?
为什么编译器不能优化浮点数与0的加法?
我有四个身份函数,基本上什么都不做。只有与1
相乘的乘法可以被clang优化为单个ret
语句。
float id0(float x) { return x + 1 - 1; } float id1(float x) { return x + 0; } float id2(float x) { return x * 2 / 2; } float id3(float x) { return x * 1; }
以下是编译器的输出结果:(clang 10,在-O3优化级别下)
.LCPI0_0: .long 1065353216 # float 1 .LCPI0_1: .long 3212836864 # float -1 id0(float): # @id0(float) addss xmm0, dword ptr [rip + .LCPI0_0] addss xmm0, dword ptr [rip + .LCPI0_1] ret id1(float): # @id1(float) xorps xmm1, xmm1 addss xmm0, xmm1 ret .LCPI2_0: .long 1056964608 # float 0.5 id2(float): # @id2(float) addss xmm0, xmm0 mulss xmm0, dword ptr [rip + .LCPI2_0] ret id3(float): # @id3(float) ret
我可以理解为什么不能优化id0
和id2
。它们增加了一个值,可能会变成正无穷大,第二个操作将无法将其改回。
但是为什么不能优化id1
?与无穷大相加会得到无穷大,与任何常规数字相加都会得到该数字,与NaN
相加会得到NaN
。那么为什么它不是像* 1
那样的“真正”的身份运算。
浮点数的加法与0的优化问题
IEEE 754浮点数有两个零值,一个是负零,一个是正零。当它们相加时,结果是正零。
所以id1(-0.f)
等于0.f
,而不是-0.f
。
需要注意的是,id1(-0.f) == -0.f
,因为0.f == -0.f
。
此外,需要注意的是,在GCC中使用-ffast-math
编译选项可以进行优化,并改变结果。
可惜,你回答得更快。这是一个用于说明的godbolt演示链接。
写得很好。我也想回答,但是你更清晰地表达了这个情况。
浮点数是计算机中用来表示实数的一种数值类型。然而,在使用浮点数进行计算时,可能会出现一些问题,其中之一就是浮点数与0进行相加时,编译器无法进行优化。本文将探讨这个问题的原因以及解决方法。
首先,我们需要了解IEEE 754浮点数标准。该标准定义了浮点数的表示方法和计算规则。在IEEE 754中,浮点数由三个部分组成:符号位、指数位和尾数位。浮点数的范围和精度都是有限的,因此在进行计算时可能会产生舍入误差。
在C语言和C++语言中,浮点数的计算通常由编译器完成。编译器在进行浮点数计算时会进行优化,以提高计算的效率。然而,当浮点数与0进行相加时,编译器无法进行优化。
这是因为0在IEEE 754中有多种表示方式。除了正常的0之外,还有负0。虽然这两个值在数学上是相等的,但在浮点数的表示中它们是不同的。正常的0表示为全零位,而负0表示为全零位并设置符号位为负。因此,当浮点数与0进行相加时,编译器无法确定要使用哪个0进行计算,从而无法进行优化。
为了解决这个问题,可以使用以下方法之一:
1. 避免使用浮点数与0进行相加的操作,而是使用其他方式实现相同的计算结果。
2. 使用特定的浮点数库,如GMPlib,来进行浮点数计算。这些库提供了更高精度的浮点数计算,可以避免舍入误差和优化问题。
总之,由于浮点数与0进行相加时编译器无法进行优化,可能会导致计算结果不准确。为了解决这个问题,可以采用其他方法实现相同的计算结果,或者使用特定的浮点数库进行计算。
编译器为什么不能优化浮点数加法与0的问题?
浮点数的加法不等于加0,而是等于加1然后减1,即`(x + 1) - 1`。如果`x`非常小,那么在`x + 1`的步骤中,可能会丢失这个非常小的部分,编译器无法知道这是否是你的意图。
同样,在`x * 2 / 2`的情况下,`x * 2`也可能不是精确的,这是由于浮点精度造成的。因此,你也面临类似的情况,编译器无法知道你是否有某种原因要以这种方式更改`x`的值。
所以以下代码是相等的:
float id0(float x) { return x + (1. - 1.); } float id1(float x) { return x + 0; }
同样,以下代码也是相等的:
float id2(float x) { return x * (2. / 2.); } float id3(float x) { return x * 1; }
当然,可以以不同的方式定义预期的行为。但正如Nelfeal所提到的,必须使用`-ffast-math`显式激活此优化。
`-ffast-math`是对于clang和gcc而言的一个特殊集合(这里列出了clang的一个选项):
-fno-honor-infinities -fno-honor-nans -fno-math-errno -ffinite-math -fassociative-math -freciprocal-math -fno-signed-zeros -fno-trapping-math -ffp-contract=fast
`x * 2`可能不是精确的,但不是因为浮点精度的问题,而是因为其范围:如果`x`过大,`x * 2`可能会产生无穷大。而`x * 2.`的情况较少发生这种情况,因为`2.`的类型是`double`,而`double`通常具有比`float`更大的范围。