Average values in c, without exceeding value data type?

224 views Asked by At

As above how can one average an array of values, without adding all into a float, this is aimed to be used in an 8 bit/16 bit micro, to alleviate time crunching floats. I have come up with a method, but I fear it is more cumbersome than adding all into a float and dividing. I will attach code below.

Average is reduced to first element in ADCsample array

Average.c

#include <xc.h>
#include "Average.h"
#include "Initialize.h"

/*
 (x+y)=((x&y)*2)+(x^y)
 (x+y)/2=(x&y)+((x^y)>>1)
 Does not overflow data type when done in pairs
 * No need for floats
 * array must have 2/4/8/16/32...values to be averaged
 * Accurate rounding
 */
void Average(void)
{
    x=0;
    for (x=0; x<ADCsamplesize; ++x)
    {
        ADCsamples[x].value=ADCsampleraw[x];
        ADCsamples[x].upflag=0;
    }
    
    x=0;
    y=0;
    z=ADCsamplesize/2;
    
    while(z!=0)
    {   
        if (((ADCsamples[y].value&0x01)^(ADCsamples[y+1].value&0x01))==1)       //is rounding needed? (even/odd mismatch)
        {
            if((ADCsamples[y].upflag^ADCsamples[y+1].upflag)==1)                //if ONE has been rounded before
            {
                    if (ADCsamples[y].upflag==1)                                //which to round down?
                    {
                        ADCsamples[y].value--;                               
                        ADCsamples[y].upflag=0;
                    }
                    else
                    {
                        ADCsamples[y+1].value--;
                        ADCsamples[y+1].upflag=0;
                    }
            }
            else                                                                //round up
            {
                if (ADCsamples[y].value!=65535)                                 //overflow protection
                {
                 ADCsamples[y].value++;                                         
                }
                else
                {
                    ADCsamples[y+1].value++;                                    //in the event of a mismatch y and y+1 cannot both be data type max
                }
                ADCsamples[x].upflag=1;                                         //mark as being rounded up
            }
        }
        else
        {
             ADCsamples[x].upflag=0;                                            //as elements are reused, clear rounded flag
        }
 
        ADCsamples[x].value=(ADCsamples[y].value&ADCsamples[y+1].value)+((ADCsamples[y].value^ADCsamples[y+1].value)>>1); //bitwise average of 2 values, not possible to overflow
        
        if (x+1!=z)                                                             
        {
            x++;
            y=y+2;
        }
        else
        {
            z=z>>1;
            x=0;
            y=0;
        }        
    }   
} 

Average.h

#ifndef AVERAGE_H
#define AVERAGE_H

#include <xc.h> // include processor files - each processor file is guarded.  

#ifdef  __cplusplus
extern "C" {
#endif /* __cplusplus */
    unsigned char z;
    unsigned char x;
    unsigned char y;
    unsigned char const ADCsamplesize=8;

    unsigned int ADCsampleraw[]=
    {
    123,516,4569,3521,852,456,981,852
    };

    typedef struct sample{
        unsigned int value;
        unsigned int upflag;
    }sample;

    sample ADCsamples[8];
    
    void Average(void);
    
#ifdef  __cplusplus
}
#endif /* __cplusplus */

#endif  /* XC_HEADER_TEMPLATE_H */

1

There are 1 answers

0
chux - Reinstate Monica On

this is aimed to be used in an 8 bit/16 bit micro, to alleviate time crunching floats.

IMO, OP has chosen a sub-optimal solution by restricting "Average values ... without exceeding value data type".

For a small micro, add the values up in an integer type that is twice the width of the samples and then divide.


If still wanting to average without a wider type and no overflow, the below does the job.

// (n-1)*(n-1) should be <= UINT_MAX
int average_unsigned(unsigned n, const unsigned a[]) {
  if (n <= 0) {
    return 0;
  }
  unsigned partial = 0;
  unsigned residue = 0;
  for (unsigned i = 0; i < n; i++) {
    partial += a[i] / n; // If n is a true constant and power-of-2, 
    residue += a[i] % n; //   these 2 steps are fast   ***
  }
  return partial + residue / n;
}

*** If n is a run-time variable, yet still a power-of-2, replace / n with >> log2n and % n with & (n-1).