如何证明更好的平均值?

时间:2013-07-06 06:08:43

标签: c algorithm

以下是Al Kelley / Ira Pohl撰写的 A Book On C(第3版)中的第60-61页的问题:

以下代码片段显示了计算运行平均值的两种不同方法:

int i;
double x;
double avg= sum= 0.0;
double navg;

for (i=1; scanf("%lf", &x)==1; ++i)
{
  avg+= (x-avg)/i;
  sum+= x;
  navg= sum/i;
}

书中写的原始问题是:如果输入一些“普通”数字,平均值和导航似乎是相同的。通过实验证明平均值更好,即使总和没有溢出。

我作为初级程序员的问题是:

  1. “更好”算法的标准是什么?我认为精确度和运行时间是两个关键因素,但还有其他一些能让算法“更好”的东西吗?

  2. 在精度和运行时间方面,我如何通过实验证明,当溢出被排除时,avg仍然是比navg更好的方法?我应该使用“与众不同”的数字,例如大小不同的数字吗?

3 个答案:

答案 0 :(得分:1)

  1. 两种算法在运行时间上没有太大差别;
  2. 与navg相比,avg的精确度更高。
  3. (1)运行时间: 以下两段代码表明,在1000000的幅度下,两种算法没有太大区别。

    #include<stdio.h>
    #include<time.h>
    
    int main()
    {
      int i ;
      double x ,sum = 0,avg = 0;
      srand(time(NULL));
      for(i = 0; i < 1000000 ; i++)
        {
          x = rand()%10+1;
          sum += x;
        }
    
      avg = sum/i;
      printf("%lf\n",avg);
      printf("time use:%lf\n",(double)clock()/CLOCKS_PER_SEC);
    }
    
    #include<stdio.h>
    #include<time.h>
    
    int main()
    {
      double sum = 0,avg = 0;
      double x;
      int i;
      srand(time(NULL));
      for(i = 0 ; i < 1000000; i++)
        {
          x = rand()%10+1;
          avg += (x-avg)/(i+1);
        }
    
      printf("%lf\n",avg);
      printf("time use:%lf\n",(double)clock()/CLOCKS_PER_SEC);
    }
    

    (2)精度: 下面的代码表明,添加avg和每个x之间的差异,结果为0;而对于navg,结果是-2.44718e-005,这意味着avg在精度上更好。

    #include <stdlib.h>
    #include <stdio.h>
    
    int main()
    {
      static double data[1000000];
      double sum, avg, check_value;
    
      int i;
      int n = sizeof(data)/sizeof(data[0]);
    
      avg = 0;
      for( i = 0; i < n; ++ i)
        {
          avg += ( data[i] - avg) / (i + 1);
        }
    
      check_value = 0;
      for( i = 0; i < n; ++ i)
        {
          check_value = check_value + ( data[i] - avg );
        }
      printf("\navg += (x[i] - avb) / i:\tavg = %g\t check_value = %g", avg, check_value );
    
      for( i = 0; i < n; ++ i )
        {
          data[i] = 1.3;
        }
    
      sum = 0;
      for( i = 0; i < n; ++ i)
        {
          sum += data[i];
        }
      avg = sum / n;
    
      check_value = 0;
      for( i = 0; i < n; ++ i)
        {
          check_value = check_value + ( data[i] - avg );
        }
      printf("\n avg = sum / N: \tavg = %g\t check_value = %g", avg, check_value );
    
      getchar();
    }
    

答案 1 :(得分:0)

请注意,即使你执行++ i

,你也会在for()循环中除以零

答案 2 :(得分:0)

我认为这是一个有效的问题,尽管措辞不太好。一个问题是,即使the question提到的furins也没有得到很好的表达,并且在收到一个好答案之前就已经关闭了。

然而问题本身是有趣的,特别是对于封闭的一个,它表明它甚至被包含在一本书中,因此它可以引导更多的人朝着一个或另一个方向。

我认为两种算法都不是特别好。在天真的平均值中,看起来我们将失去精确度,或者当平均具有多个差异的数字时我们甚至会丢失数字,但是也可能用其他算法发现相同的结果,可能只是使用不同的输入数据集。

所以,特别是因为它来自现有的书,我认为这是一个非常有效的问题,寻求一些体面的答案。

我试图通过一个例子来掩盖我对这两种算法的看法。所以想象一下你有4个大小相同的数字,你想平均它们。

天真的方法首先将它们一个接一个地总结。在对前两个求和之后,你显然在低端失去了一点精度(因为你现在可能有一个更大的指数)。当您添加最后一个数字时,您有2位丢失(现在使用哪些位来表示总和的高位)。但是你除以4,在这种情况下基本上只是从你的指数中减去2。

在此过程中我们失去了什么?现在,如果所有数字首先被截断2位,则更容易回答。这种情况显然是得到的平均值的最后两位将为零,并且可能引入额外的2位错误(如果所有截断的位都恰好是原始数字中的那些比较,如果它们是零)。所以基本上如果源是具有23位分数的单精度浮点数,那么得到的平均值将具有大约19位的精度。

天真方法的实际结果更好,但总和的第一个数字并没有那么精确。

在每次迭代的差分方法中,将适当加权的差值加到总和中。如果数字具有相同的幅度,那么这种差异很可能会低于一个数量级。然后将其除以当前计数,在此操作中没有任何损失,但是最后一个数字的结果差异(在该示例中i = 4)可能比源数量低约3个数量级。我们将其添加到与原始数字大小相同的运行平均值。

因此,使用此示例中的差分方法添加最后一个数字似乎已经丢失了大约3位的精度,对于所有4个数字,它甚至可能看起来像我们可能会降低到5个基本上丢失的精度 - 可能更糟糕比天真的方法?

差分方法更难以遵循,也许我在我的假设中做了一些错误。但我认为很清楚:看待一个或另一个表现更好似乎没有效果,或者如果是这样,可能取决于数据的布局和幅度差异。