促使我在看这个问题也是stackoverflow的一个问题:
gcc为什么不优化aaaaaa为(aaa)*(aaa)
楼主的意思是向这种偶数次的幂运算,我们这样只做一半的浮点乘法不是更快吗?
对啊,我也觉得呢。。
下面就有人指出了:
Because Floating Point Math is not Associative. The way you group the operands in floating point multiplication has an effect on the numerical accuracy of the answer.
As a result, most compilers are very conservative about reordering floating point calculations unless they can be sure that the answer will stay the same, or unless you tell them you don't care about numerical accuracy. For example: the -fassociative-math
option of gcc which allows gcc to reassociate floating point operations, or even the -ffast-math
option which allows even more aggressive tradeoffs of accuracy against speed.
Lambdageek correctly points out that because associativity does not hold for floating-point numbers, the "optimization" of aaaaaa to (aaa)(aaa)may change the value.
就像我下面会介绍的一样,浮点运算实际上是一个很复杂的东西,首先浮点数并不精确,只是用近似值去代替;
其次浮点运算里面会发生溢出,舍入的情况,导致出现意想不到的结果。
把aaaaaa优化为(aaa)*(aaa)就有可能会出错哦,所以编译器还是采取稳妥一点的思路比较好。
然后还有人补充了,即使是普通的加法运算,编译器也不会利用加法的结合性去优化(这一点下面也提到了),通俗的解释就是“大数吃小数”。
Another similar case: most compilers won't optimize a + b + c + d
to (a + b) + (c + d)
(this is an optimization since the second expression can be pipelined better) and evaluate it as given (i.e. as (((a + b) + c) + d)
). This too is because of corner cases:
float a = 1e35, b = 1e-5, c = -1e35, d = 1e-5; printf("%e %e\n", a + b + c + d, (a + b) + (c + d));
This outputs 1.000000e-05 0.000000e+00
IEEE浮点表示:
使用如下形式:
- 符号s(sign)表示符号
- 有效数significand M是一个二进制小数,它的范围在1-2间或0-1间
- 指数exponent E是2的幂(可以是负数)
浮点数的位被划分为三个域,以编码这些值: - 一个单独的符号s位直接编码s
-
k位的指数域
编码指数E
-
n位小数域
编码有效数M
在floate中,s,exp,frac分别为 1位,k=8位,n=23位
在double中,s,exp,frac分别为 1位,k=11位,n=52位
c语言中的浮点类型强制转换:
溢出与范围有关,舍入与精度有关
int->float 数字不会溢出,可能被舍入
int/float->double double范围大,所以会被精确的保留
double->float 可能溢出位无穷大,还可能被舍入
float/double->int 值向0截断,比如1.999变为1;还可能会溢出
在Intel处理器的计算机中,由于处理器内部含有寄存器,而浮点寄存器使用特殊的80位扩展精度格式。
这意味着在寄存器中存储一个值,就会在读进读出的时候产生舍入,溢出,数字的值会被改变。
使用浮点运算的时候要小心,因为浮点运算的范围和精度有限,而且浮点运算不遵守普遍的算术属性,比如结合性。