繁体   English   中英

浮点乘法与重复加法

[英]floating point multiplication vs repeated addition

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关联数学,我发现GCC可以按log2(N)步骤减少这一点。 例如,对于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总和。

我的问题是为什么GCC不用-Ofasta*(float)N

它可以简单地为O(1)而不是O(N)O(Log(N)) O(1) 由于N在编译时是已知的,因此可以确定N是否适合浮点数。 即使N对于浮点数来说太大也可以得到sum =a*(float)(N & 0x0000ffff) + a*(float)(N & ffff0000) 事实上,我做了一些测试来检查准确性,无论如何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值都不会导致重复加法的总和没有变化。 对于乘法选择,结果(值* N)需要FLT_EPSILON *小于具有无操作的操作的总和。

因此编译器无法进行优化,因为它无法判断您是否需要确切的行为(乘法更好),或者实现的行为,其中sum的大小会影响加法的结果。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM