浮点乘法与重复加法
设N
是编译时无符号整数。
GCC可以优化
unsigned sum = 0; for(unsigned i=0; i<N; i++) sum += a; // a is an unsigned integer
只是a*N
这是可以理解的,因为模运算表示(a%k + b%k)%k = (a+b)%k
。
但是GCC不会优化
float sum = 0; for(unsigned i=0; i<N; i++) sum += a; // a is a float
到a*(float)N
。
但通过使用例如-Ofast
关联math,我发现GCC可以按log2(N)
步骤来减less这个。 例如,对于N=8
它可以三次加法求和。
sum = a + a sum = sum + sum // (a + a) + (a + a) sum = sum + sum // ((a + a) + (a + a)) + ((a + a) + (a + a))
虽然N=16
GCC之后的某些点可以追溯到N-1
和。
我的问题是为什么海湾合作委员会不用-Ofast
做a*(float)N
?
而不是O(N)
或O(Log(N))
它可能只是O(1)
。 由于N
在编译时是已知的,因此可以确定N
是否适合于浮点数。 即使N
对于浮点数来说太大,它也可以做sum =a*(float)(N & 0x0000ffff) + a*(float)(N & ffff0000)
。 事实上,我做了一些testing来检查准确性,无论如何, a*(float)N
是更准确的(见下面的代码和结果)。
//gcc -O3 foo.c //don't use -Ofast or -ffast-math or -fassociative-math #include <stdio.h> float sumf(float a, int n) { float sum = 0; for(int i=0; i<n; i++) sum += a; return sum; } float sumf_kahan(float a, int n) { float sum = 0; float c = 0; for(int i=0; i<n; i++) { float y = a - c; float t = sum + y; c = (t -sum) - y; sum = t; } return sum; } float mulf(float a, int n) { return a*n; } int main(void) { int n = 1<<24; float a = 3.14159; float t1 = sumf(a,n); float t2 = sumf_kahan(a,n); float t3 = mulf(a,n); printf("%f %f %f\n",t1,t2,t3); }
结果是61848396.000000 52707136.000000 52707136.000000
这表明乘法和Kahan总结有相同的结果,我认为这表明乘法比简单的总和更准确。
有一些根本的区别
float funct( int N, float sum ) { float value = 10.0; for( i = 0; i < N ;i ++ ) { sum += value; } return sum; }
和
float funct( int N, float sum ) { float value = 10.0; sum += value * N; return sum; }
当总和接近大于值的FLT_EPSILON *时,重复总和倾向于无操作。 所以N的任何大的值都不会导致重复加和的总和。 对于乘法select,结果(值* N)需要为小于总和的FLT_EPSILON *,以使操作具有无操作。
因此,编译器无法进行优化,因为它不能确定是否需要确切的行为(乘法更好),或执行的行为,其中和的比例影响相加的结果。