數學之美:均值計算的兩種算法(C實現)


關注、星標嵌入式客棧,精彩及時送達

[導讀] 在嵌入式產品開發中,有時會需要利用一些數學統計的一些知識,並利用代碼的方式實施在產品的應用中。有人會說均值有啥好聊的,不就是加起來除一下嘛?不妨來讀一讀。

本文目的不是數學,而在於分享如何進行工程應用實現。

什么是均值?

對於離散數據集,算術平均值也稱為期望值或簡稱為平均值,是離散數據集合的中心值。假設有這樣的數據序列: ,其均值由下面的式子計算:

對,你說的沒錯就是加起來求平均。有盆友或許會問,為什么均值有的地方寫的是 ,而這里寫成 ,這其實是有緣由的:

  • 樣本均值(sample mean):某類隨機變量有限樣本的算術平均值。

  • 總體均值(population mean):從隨機變量概率分布的角度對隨機變量趨勢的度量,所以從這個角度而言,下面的公式正是描述了這個概念:

弄這樣兩個概念有什么必要呢?總體均值反應的是事務的總體規律,實際研究中,往往很難得到所有的數據,比如產品的某項指標規律,如果每一個產品都去測,代價可能極其高昂,實際往往是對產品進行抽樣檢測。(公式中多寫了個X)

大數定律指出樣本數量越大,樣本均值越接近總體均值

如此一來這就有實用意義了。

均值除了上面這種算術平均值之外,還有幾何平均值、諧波均值、功率均值、加權均值、截斷均值、函數泛化均值等,有興趣的可以去了解一下。

如何計算均值?

這里主要討論對於嵌入式電子系統編程中,樣本均值的計算方法以及C代碼。分享直接法和遞推法計算均值,重點介紹遞推法。

直接法

按照公式定義,加和求平均。這個編代碼很容易:

float mean(float *pSample,int size)
{
   if(pSample==NULL || size<=0)
       return NAN;
  
   float sum = 0;
   for(int i=0;i<size;i++)
   {
     sum += *pSample;
     pSample++;
   }

   return (sum/size);
}

該方法簡單直接,但是缺點是在內存比較小的單片機系統中如需要計算大樣本集的場合,就捉襟見肘了。比如幾萬樣本時,內存可能就不夠了!

遞推法

因為樣本均值計算公式如下:

那么前 個樣本的均值為:

不難得出:

從而

這樣就可以編代碼了:

float recursive_mean(float xn,int size)
{
  static int index = 0;
  static float last_mean = 0.0f;
  float mean = 0.0f;

  if(index<size-1)
  {
    index++;
    mean = last_mean+(xn-last_mean)/index;
  }
  else
  {
    mean = last_mean+(xn-last_mean)/size;
    index     = 0;
    last_mean = 0.0f;
  }

  last_mean = mean;
  return mean;
}

這個代碼很容易理解:

  • 在樣本窗未滿時,按實際傳入樣本大小遞推。

  • 在樣本窗滿后,按實際傳入樣本大小遞推,並復位索引。

函數內靜態變量不推薦使用,但這里函數使用了內部靜態變量,為什么使用靜態變量呢?因為所實現的需求對外部不可見,這種需求本身的作用域就在函數本體內部。這樣寫個人理解會更好一些。

關於static的用法,前面寫過兩篇文章,有興趣的可以去點進去看看:

在嵌入式應用中,如果所需要統計的樣本非常大時,這種算法將非常有實用價值,只需要極小的內存開銷。尤其在一些傳感器測量應用中,該方法非常有價值。

測試一下

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

float mean(float *pSample,int size)
{
   if(pSample==NULL || size<=0)
       return NAN;
   float sum = 0;
   for(int i=0;i<size;i++)
   {
     sum += *pSample;
     pSample++;
   }

   return (sum/size);
}

float recursive_mean(float xn,int size)
{
  static int index = 0;
  static float last_mean = 0.0f;
  float mean = 0.0f;

  if(index<size-1)
  {
    index++;
    mean = last_mean+(xn-last_mean)/index;
  }
  else
  {
    mean = last_mean+(xn-last_mean)/size;
    index     = 0;
    last_mean = 0.0f;
  }

  last_mean = mean;
  return mean;
}
#define N            (1000)
#define SAMPLE_SIZE  (100)
int main(int argc, char *argv[])
{
    float sim[N];
    float out[N/SAMPLE_SIZE];

    for(int i=0;i<N;i++)
    {
        sim[i]=i*5+rand()%10;
    }
    printf("\n\n");
    int j=0;
    for(int i=0;i<N;i=i+SAMPLE_SIZE)
    {
        out[j] = mean(&sim[i],SAMPLE_SIZE);
        j++;
    }

    for(j=0;j<N/SAMPLE_SIZE;j++)
    {
        printf("%.2f,",out[j]);
    }
    printf("\n");
    j = 0;
    for(int i=0;i<N;i++)
    {
        out[j]=recursive_mean(sim[i],SAMPLE_SIZE);

        if((i+1)%SAMPLE_SIZE==0)
            j++;
    }
    for(j=0;j<N/SAMPLE_SIZE;j++)
    {
        printf("%.2f,",out[j]);
    }
    printf("\n");
    return 0;
}

看一下結果:

252.14,752.38,1251.85,1751.82,2252.57,2752.25,3251.78,3751.58,4252.06,4752.02,
252.14,752.38,1251.85,1751.82,2252.57,2752.25,3251.78,3751.58,4252.06,4752.02,

兩種計算方法效果一樣,但是第二種方法消耗極小的內存。當然這增加了函數調用次數,但是在大樣本計算時非常有利。

總結一下

在實際應用中,常常需要求取測量的均值,或者依據均值做相應的應用,而且均值是計算信號序列或者樣本集其他數學統計規律的基礎計算,比如要計算方差、協方差等等。那么實際應用中學會如何計算均值並進行編碼實現時很必要的。尤其在一些內存受限的場景,學會利用遞推規律進行計算很有學習掌握的價值。

END

往期精彩推薦,點擊即可閱讀

▲Linux驅動相關專輯 

手把手教信號處理專輯

片機相關專輯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM