浮点乘法与重复加法

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和。

我的问题是为什么海湾合作委员会不用-Ofasta*(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 *,以使操作具有无操作。

因此,编译器无法进行优化,因为它不能确定是否需要确切的行为(乘法更好),或执行的行为,其中和的比例影响相加的结果。