簡體   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