Skip to content

Commit

Permalink
Use dynamic memory to support large internal buffers (#6)
Browse files Browse the repository at this point in the history
* make dynamic memory default
* renamed internal variables less cryptic
  • Loading branch information
RobTillaart authored Jan 4, 2021
1 parent 7b6a6d0 commit bfbd4ff
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 91 deletions.
124 changes: 68 additions & 56 deletions RunningMedian.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// FILE: RunningMedian.cpp
// AUTHOR: Rob.Tillaart at gmail.com
// VERSION: 0.2.2
// AUTHOR: Rob Tillaart
// VERSION: 0.3.0
// PURPOSE: RunningMedian library for Arduino
//
// HISTORY:
Expand All @@ -12,7 +12,7 @@
// 0.1.04 2013-10-17 added getAverage(uint8_t) - kudo's to Sembazuru
// 0.1.05 2013-10-18 fixed bug in sort; removes default constructor; dynamic memory
// 0.1.06 2013-10-19 faster sort, dynamic arrays, replaced sorted float array with indirection array
// 0.1.07 2013-10-19 add correct median if _cnt is even.
// 0.1.07 2013-10-19 add correct median if _count is even.
// 0.1.08 2013-10-20 add getElement(), add getSottedElement() add predict()
// 0.1.09 2014-11-25 float to double (support ARM)
// 0.1.10 2015-03-07 fix clear
Expand All @@ -24,161 +24,173 @@
// 0.2.0 2020-04-16 refactor.
// 0.2.1 2020-06-19 fix library.json
// 0.2.2 2021-01-03 add Arduino-CI + unit tests
// 0.3.0 2021-01-04 malloc memory as default storage


#include "RunningMedian.h"


RunningMedian::RunningMedian(const uint8_t size)
{
_size = constrain(size, MEDIAN_MIN_SIZE, MEDIAN_MAX_SIZE);

#ifdef RUNNING_MEDIAN_USE_MALLOC
_ar = (float *) malloc(_size * sizeof(float));
_p = (uint8_t *) malloc(_size * sizeof(uint8_t));
_values = (float *) malloc(_size * sizeof(float));
_sortIdx = (uint8_t *) malloc(_size * sizeof(uint8_t));
#endif

clear();
}


RunningMedian::~RunningMedian()
{
#ifdef RUNNING_MEDIAN_USE_MALLOC
free(_ar);
free(_p);
#endif
#ifdef RUNNING_MEDIAN_USE_MALLOC
free(_values);
free(_sortIdx);
#endif
}

// resets all counters

// resets all internal counters
void RunningMedian::clear()
{
_cnt = 0;
_idx = 0;
_count = 0;
_index = 0;
_sorted = false;
for (uint8_t i = 0; i < _size; i++)
{
_p[i] = i;
_sortIdx[i] = i;
}
}


// adds a new value to the data-set
// or overwrites the oldest if full.
void RunningMedian::add(float value)
{
_ar[_idx++] = value;
if (_idx >= _size) _idx = 0; // wrap around
if (_cnt < _size) _cnt++;
_values[_index++] = value;
if (_index >= _size) _index = 0; // wrap around
if (_count < _size) _count++;
_sorted = false;
}


float RunningMedian::getMedian()
{
if (_cnt == 0) return NAN;
if (_count == 0) return NAN;

if (_sorted == false) sort();

if (_cnt & 0x01) // is it odd sized?
if (_count & 0x01) // is it odd sized?
{
return _ar[_p[_cnt / 2]];
return _values[_sortIdx[_count / 2]];
}
return (_ar[_p[_cnt / 2]] + _ar[_p[_cnt / 2 - 1]]) / 2;
return (_values[_sortIdx[_count / 2]] + _values[_sortIdx[_count / 2 - 1]]) / 2;
}


float RunningMedian::getQuantile(float q)
{
if (_cnt == 0) return NAN;
if (_count == 0) return NAN;

if ((q < 0) || (q > 1)) return NAN;

if (_sorted == false) sort();

const float id = (_cnt - 1) * q;
const uint8_t lo = floor(id);
const uint8_t hi = ceil(id);
const float qs = _ar[_p[lo]];
const float h = (id - lo);

return (1.0 - h) * qs + h * _ar[_p[hi]];
const float id = (_count - 1) * q;
const uint8_t lo = floor(id);
const uint8_t hi = ceil(id);
const float qs = _values[_sortIdx[lo]];
const float h = (id - lo);

return (1.0 - h) * qs + h * _values[_sortIdx[hi]];
}


float RunningMedian::getAverage()
{
if (_cnt == 0) return NAN;
if (_count == 0) return NAN;

float sum = 0;
for (uint8_t i = 0; i < _cnt; i++)
for (uint8_t i = 0; i < _count; i++)
{
sum += _ar[i];
sum += _values[i];
}
return sum / _cnt;
return sum / _count;
}


float RunningMedian::getAverage(uint8_t nMedians)
{
if ((_cnt == 0) || (nMedians == 0)) return NAN;
if ((_count == 0) || (nMedians == 0)) return NAN;

if (_cnt < nMedians) nMedians = _cnt; // when filling the array for first time
uint8_t start = ((_cnt - nMedians) / 2);
if (_count < nMedians) nMedians = _count; // when filling the array for first time
uint8_t start = ((_count - nMedians) / 2);
uint8_t stop = start + nMedians;

if (_sorted == false) sort();

float sum = 0;
for (uint8_t i = start; i < stop; i++)
{
sum += _ar[_p[i]];
sum += _values[_sortIdx[i]];
}
return sum / nMedians;
}


float RunningMedian::getElement(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt)) return NAN;
if ((_count == 0) || (n >= _count)) return NAN;

uint8_t pos = _idx + n;
if (pos >= _cnt) // faster than %
uint8_t pos = _index + n;
if (pos >= _count) // faster than %
{
pos -= _cnt;
pos -= _count;
}
return _ar[pos];
return _values[pos];
}


float RunningMedian::getSortedElement(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt)) return NAN;
if ((_count == 0) || (n >= _count)) return NAN;

if (_sorted == false) sort();
return _ar[_p[n]];
return _values[_sortIdx[n]];
}


// n can be max <= half the (filled) size
float RunningMedian::predict(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt / 2)) return NAN;
uint8_t mid = _count / 2;
if ((_count == 0) || (n >= mid)) return NAN;

float med = getMedian(); // takes care of sorting !
if (_cnt & 0x01)
if (_count & 0x01) // odd # elements
{
return max(med - _ar[_p[_cnt / 2 - n]], _ar[_p[_cnt / 2 + n]] - med);
return max(med - _values[_sortIdx[mid - n]], _values[_sortIdx[mid + n]] - med);
}
float f1 = (_ar[_p[_cnt / 2 - n]] + _ar[_p[_cnt / 2 - n - 1]]) / 2;
float f2 = (_ar[_p[_cnt / 2 + n]] + _ar[_p[_cnt / 2 + n - 1]]) / 2;
// even # elements
float f1 = (_values[_sortIdx[mid - n]] + _values[_sortIdx[mid - n - 1]]) / 2;
float f2 = (_values[_sortIdx[mid + n]] + _values[_sortIdx[mid + n - 1]]) / 2;
return max(med - f1, f2 - med) / 2;
}


void RunningMedian::sort()
{
// bubble sort with flag
for (uint8_t i = 0; i < _cnt - 1; i++)
for (uint8_t i = 0; i < _count - 1; i++)
{
bool flag = true;
for (uint8_t j = 1; j < _cnt - i; j++)
for (uint8_t j = 1; j < _count - i; j++)
{
if (_ar[_p[j - 1]] > _ar[_p[j]])
if (_values[_sortIdx[j - 1]] > _values[_sortIdx[j]])
{
uint8_t t = _p[j - 1];
_p[j - 1] = _p[j];
_p[j] = t;
uint8_t t = _sortIdx[j - 1];
_sortIdx[j - 1] = _sortIdx[j];
_sortIdx[j] = t;
flag = false;
}
}
Expand Down
72 changes: 39 additions & 33 deletions RunningMedian.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#pragma once
//
// FILE: RunningMedian.h
// AUTHOR: Rob dot Tillaart at gmail dot com
// AUTHOR: Rob Tillaart
// PURPOSE: RunningMedian library for Arduino
// VERSION: 0.2.2
// VERSION: 0.3.0
// URL: https://github.com/RobTillaart/RunningMedian
// URL: http://arduino.cc/playground/Main/RunningMedian
// HISTORY: See RunningMedian.cpp
Expand All @@ -12,72 +12,78 @@

#include "Arduino.h"

#define RUNNING_MEDIAN_VERSION (F("0.2.2"))
#define RUNNING_MEDIAN_VERSION (F("0.3.0"))

// prepare for dynamic version
// not tested ==> use at own risk :)
#define RUNNING_MEDIAN_USE_MALLOC

// fall back to fixed storage for dynamic version => remove true
#define RUNNING_MEDIAN_USE_MALLOC true

// should at least be 5 to be practical,
// odd sizes results in a 'real' middle element and will be a bit faster.
// even sizes takes the average of the two middle elements as median
#define MEDIAN_MIN_SIZE 5

// allow compile time (command line) setting of MEDIAN_MAX_SIZE
#ifndef MEDIAN_MAX_SIZE
#define MEDIAN_MAX_SIZE 19
// MEDIAN_MIN_SIZE should at least be 3 to be practical,
#define MEDIAN_MIN_SIZE 3


#ifdef RUNNING_MEDIAN_USE_MALLOC
// max 250 to not overflow uint8_t internal vars
#define MEDIAN_MAX_SIZE 250
#else
// using fixed memory will be limited to 19 elements.
#define MEDIAN_MAX_SIZE 19
#endif


class RunningMedian
{
public:
// # elements in the internal buffer
// odd sizes results in a 'real' middle element and will be a bit faster.
// even sizes takes the average of the two middle elements as median
explicit RunningMedian(const uint8_t size);
~RunningMedian();

// resets internal buffer and var
void clear();
void clear();
// adds a new value to internal buffer, optionally replacing the oldest element.
void add(const float value);
void add(const float value);
// returns the median == middle element
float getMedian();
float getMedian();

// returns the Quantile
float getQuantile(const float q);
float getQuantile(const float q);

// returns average of the values in the internal buffer
float getAverage();
float getAverage();
// returns average of the middle nMedian values, removes noise from outliers
float getAverage(uint8_t nMedian);
float getAverage(uint8_t nMedian);

float getHighest() { return getSortedElement(_cnt - 1); };
float getLowest() { return getSortedElement(0); };
float getHighest() { return getSortedElement(_count - 1); };
float getLowest() { return getSortedElement(0); };

// get n'th element from the values in time order
float getElement(const uint8_t n);
float getElement(const uint8_t n);
// get n'th element from the values in size order
float getSortedElement(const uint8_t n);
float getSortedElement(const uint8_t n);
// predict the max change of median after n additions
float predict(const uint8_t n);
float predict(const uint8_t n);

uint8_t getSize() { return _size; };
uint8_t getSize() { return _size; };
// returns current used elements, getCount() <= getSize()
uint8_t getCount() { return _cnt; };
uint8_t getCount() { return _count; };


protected:
boolean _sorted;
uint8_t _size;
uint8_t _cnt;
uint8_t _idx;
boolean _sorted; // _sortIdx{} is up to date
uint8_t _size; // max number of values
uint8_t _count; // current number of values
uint8_t _index; // next index to add.

// _values holds the elements themself
// _p holds the index for sorted
#ifdef RUNNING_MEDIAN_USE_MALLOC
float * _ar;
uint8_t * _p;
float * _values;
uint8_t * _sortIdx;
#else
float _ar[MEDIAN_MAX_SIZE];
float _values[MEDIAN_MAX_SIZE];
uint8_t _p[MEDIAN_MAX_SIZE];
#endif
void sort();
Expand Down
27 changes: 27 additions & 0 deletions examples/RunningMedian_large/RunningMedian_large.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// FILE: RunningMedian2.ino
// AUTHOR: Rob Tillaart ( kudos to Sembazuru)
// VERSION: 0.1.2
// PURPOSE: demo most functions
// DATE: 2013-10-17
// URL: https://github.com/RobTillaart/RunningMedian
//

#include "RunningMedian.h"

RunningMedian samples = RunningMedian(101);

long count = 0;

void setup()
{
Serial.begin(115200);
Serial.print(F("Running Median Version: "));
Serial.println(RUNNING_MEDIAN_VERSION);

Serial.println(samples.getSize());
}

void loop()
{
}
Loading

0 comments on commit bfbd4ff

Please sign in to comment.