0.4.6 RunningAverage

This commit is contained in:
Rob Tillaart 2024-06-29 09:22:03 +02:00
parent 650a3c45f0
commit 40a0f8123f
12 changed files with 218 additions and 51 deletions

View File

@ -6,11 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.4.6] - 2024-06-15
- Fix #30, add **float getSum()** (thanks to heidnerd)
- Fix #31, add **float getStandardDeviationLast(uint16_t count)** (thanks to alvaro-oliver)
- added code to detect inner array == NULL in more functions (return NAN).
- changed return type **bool clear()**
- changed return type **bool addValue(..)**
- changed return type **bool fillValue(..)**
- changed return type **bool setPartial(..)**
- moved performance.txt to performance sketch
- update unit test
- update keywords.txt
- update readme.md
- minor edits.
## [0.4.5] - 2024-01-05
- fix URL in examples
- minor edits
## [0.4.4] - 2023-10-18
- update readme.md badges
- update examples
@ -29,7 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- minor edits
## [0.4.1] - 2021-11-22
- updated buil-CI, readme, badges
- updated build-CI, readme, badges
- add getAverageLast() functions.
## [0.4.0] - 2021-05-18
@ -57,7 +70,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.2.15] - 2020-01-17
- fix overflow in getValue - see issue #139
## [0.2.14] - 2020-01-15
- added getValue(n) to retrieve elements in order of addition - see issue #132

View File

@ -58,12 +58,19 @@ No default size (yet).
### Basic
- **void clear()** empties the internal buffer.
- **void add(float value)** wrapper for **addValue()**
- **void addValue(float value)** adds a new value to the object, if the internal buffer is full,
The following functions return **false** if the internal buffer is not allocated.
- **bool clear()** empties the internal buffer.
- **bool add(float value)** wrapper for **addValue()**.
- **bool addValue(float value)** adds a new value to the object, if the internal buffer is full,
the oldest element is removed.
- **void fillValue(float value, uint16_t number)** adds number elements of value.
- **bool fillValue(float value, uint16_t number)** adds number elements of value.
Good for initializing the system to a certain starting average.
The following functions returns NAN if there are no values present (count == 0) or
of internal array is not allocated.
- **float getValue(uint16_t position)** returns the value at **position** from the additions.
Position 0 is the first one to disappear.
- **float getAverage()** iterates over all elements to get the average, slower but accurate.
@ -80,6 +87,7 @@ Needs more than one element to be calculable.
- **float getMax()** returns maximum since last clear, does not need to be in the buffer any more.
- **float getMinInBuffer()** returns minimum in the internal buffer.
- **float getMaxInBuffer()** returns maximum in the internal buffer.
- **float getSum()** returns sum of values in the internal buffer.
### Admin functions
@ -92,9 +100,10 @@ Needs more than one element to be calculable.
## Partial functions
- **void setPartial(uint16_t partial = 0)** use only a part of the internal array.
- **bool setPartial(uint16_t partial = 0)** use only a part of the internal array.
Allows to change the weight and history factor.
0 ==> use all == default.
Returns false if internal buffer is not allocated.
- **uint16_t getPartial()** returns the set value for partial.
@ -122,6 +131,33 @@ parameter, the functions will return the statistics of the whole buffer.
- **float getAverageSubset(uint16_t start, uint16_t count)**
Get the average of subset - count elements from start.
Returns NAN if no elements or internal array not allocated.
## Performance
Indicative performance on an UNO, see examples.
| Function | 0.4.5 us | 0.4.6 us | Notes |
|:----------------------:|:----------:|:----------:|:-------:|
| clear | 60 | 60 |
| addValue | 24 | 24 |
| fillValue | 1512 | 1520 |
| getValue | 4 | 8 |
| getAverage | 520 | 552 |
| getFastAverage | 36 | 40 |
| getStandardDeviation | 1856 | 1856 |
| getStandardError | 1920 | 1920 |
| getMin | 8 | 4 |
| getMax | 4 | 4 |
| getMinInBuffer | 216 | 212 |
| getMaxInBuffer | 208 | 208 |
| getSum | - | 8 |
| bufferIsFull | 8 | 8 |
| getElement | 4 | 4 |
| getSize | 8 | 8 |
| getCount | 8 | 8 |
| last functions | - | - | not tested
## Operation
@ -131,7 +167,6 @@ See examples
## Future
#### Must
- update documentation, explain better
@ -139,16 +174,22 @@ See examples
#### Should
- check for optimizations.
- divide by count happens often ...
- clear(bool zero = true) to suppress setting all to 0. ?
- divide by count happens often
- fillValue can be optimized (See #13)
- ```temp = sqrt(temp/(_count - 1));``` is this correct STDDEV?
- divide by count or count - 1?
- https://www.zaner.com/3.0/education/technicalstudies/MSD.asp
#### Could
- create a double based derived class? Template class?
- create a double based derived class?
- Template class?
- add error handling (important?).
- investigate **modus()** most frequently occurring value.
- difficult with floats ?
- what to do when on two or more values are on par?
- what to do when on two or more values are on par? (no modus?)
- **int getUniqueValuesInBuffer()** O(n^2)
#### Wont

View File

@ -1,7 +1,7 @@
//
// FILE: RunningAverage.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.4.5
// VERSION: 0.4.6
// DATE: 2011-01-30
// PURPOSE: Arduino library to calculate the running average by means of a circular buffer
// URL: https://github.com/RobTillaart/RunningAverage
@ -30,26 +30,31 @@ RunningAverage::~RunningAverage()
// resets all counters
void RunningAverage::clear()
bool RunningAverage::clear()
{
_count = 0;
_index = 0;
_sum = 0.0;
_min = NAN;
_max = NAN;
if (_array == NULL)
{
return false;
}
for (uint16_t i = _size; i > 0; )
{
_array[--i] = 0.0; // keeps addValue simpler
}
return true;
}
// adds a new value to the data-set
void RunningAverage::addValue(const float value)
bool RunningAverage::addValue(const float value)
{
if (_array == NULL)
{
return;
return false;
}
_sum -= _array[_index];
@ -66,13 +71,15 @@ void RunningAverage::addValue(const float value)
// update count as last otherwise if ( _count == 0) above will fail
if (_count < _partial) _count++;
return true;
}
// returns the average of the data-set added so far, NAN if no elements.
// returns the average of the data-set added so far,
// returns NAN if no elements or missing array
float RunningAverage::getAverage()
{
if (_count == 0)
if ((_count == 0) || (_array == NULL))
{
return NAN;
}
@ -94,7 +101,6 @@ float RunningAverage::getFastAverage() const
{
return NAN;
}
return _sum / _count; // multiplication is faster ==> extra admin
}
@ -102,7 +108,7 @@ float RunningAverage::getFastAverage() const
// returns the minimum value in the buffer
float RunningAverage::getMinInBuffer() const
{
if (_count == 0)
if ((_count == 0) || (_array == NULL))
{
return NAN;
}
@ -119,7 +125,7 @@ float RunningAverage::getMinInBuffer() const
// returns the maximum value in the buffer
float RunningAverage::getMaxInBuffer() const
{
if (_count == 0)
if ((_count == 0) || (_array == NULL))
{
return NAN;
}
@ -136,11 +142,10 @@ float RunningAverage::getMaxInBuffer() const
// returns the value of an element if exist, NAN otherwise
float RunningAverage::getElement(uint16_t index) const
{
if (_count == 0)
if ((_count == 0) || (_array == NULL))
{
return NAN;
}
return _array[index];
}
@ -161,6 +166,7 @@ float RunningAverage::getStandardDeviation() const
{
temp += pow((_array[i] - average), 2);
}
// TODO: when to divide by count || count-1?
temp = sqrt(temp/(_count - 1));
return temp;
// see issue #13
@ -188,9 +194,12 @@ float RunningAverage::getStandardError() const
// fill the average with the same value number times. (weight)
// This is maximized to size times.
// no need to fill the internal buffer over 100%
void RunningAverage::fillValue(const float value, const uint16_t number)
bool RunningAverage::fillValue(const float value, const uint16_t number)
{
clear();
if (!clear())
{
return false;
}
uint16_t s = number;
if (s > _partial) s = _partial;
@ -198,6 +207,7 @@ void RunningAverage::fillValue(const float value, const uint16_t number)
{
addValue(value);
}
return true;
}
@ -223,7 +233,7 @@ void RunningAverage::fillValue(const float value, const uint16_t number)
float RunningAverage::getValue(const uint16_t position)
{
if (_count == 0)
if ((_count == 0) || (_array == NULL))
{
return NAN;
}
@ -238,11 +248,11 @@ float RunningAverage::getValue(const uint16_t position)
}
void RunningAverage::setPartial(const uint16_t partial)
bool RunningAverage::setPartial(const uint16_t partial)
{
_partial = partial;
if ((_partial == 0) || (_partial > _size)) _partial = _size;
clear();
return clear();
}
@ -264,6 +274,28 @@ float RunningAverage::getAverageLast(uint16_t count)
}
float RunningAverage::getStandardDeviationLast(uint16_t count)
{
uint16_t cnt = count;
if (cnt > _count) cnt = _count;
if (cnt <= 1) return NAN;
float temp = 0;
float average = getAverageLast(count);
uint16_t idx = _index;
for (uint16_t i = 0; i < cnt; i++)
{
if (idx == 0) idx = _size;
idx--;
temp += pow((_array[idx] - average), 2);
}
temp = sqrt(temp/(cnt - 1));
return temp;
}
float RunningAverage::getMinInBufferLast(uint16_t count)
{
uint16_t cnt = count;
@ -306,7 +338,7 @@ float RunningAverage::getMaxInBufferLast(uint16_t count)
float RunningAverage::getAverageSubset(uint16_t start, uint16_t count)
{
if (_count == 0)
if ((_count == 0) || (_array == NULL))
{
return NAN;
}

View File

@ -2,7 +2,7 @@
//
// FILE: RunningAverage.h
// AUTHOR: Rob Tillaart
// VERSION: 0.4.5
// VERSION: 0.4.6
// DATE: 2011-01-30
// PURPOSE: Arduino library to calculate the running average by means of a circular buffer
// URL: https://github.com/RobTillaart/RunningAverage
@ -14,7 +14,7 @@
#include "Arduino.h"
#define RUNNINGAVERAGE_LIB_VERSION (F("0.4.5"))
#define RUNNINGAVERAGE_LIB_VERSION (F("0.4.6"))
class RunningAverage
@ -23,10 +23,11 @@ public:
explicit RunningAverage(const uint16_t size);
~RunningAverage();
void clear();
void add(const float value) { addValue(value); };
void addValue(const float value);
void fillValue(const float value, const uint16_t number);
// return false if internal buffer not allocated.
bool clear();
bool add(const float value) { return addValue(value); };
bool addValue(const float value);
bool fillValue(const float value, const uint16_t number);
float getValue(const uint16_t position);
float getAverage(); // iterates over all elements.
@ -40,9 +41,10 @@ public:
float getMin() const { return _min; };
float getMax() const { return _max; };
// returns min/max from the values in the internal buffer
// returns min/max/sum from the values in the internal buffer
float getMinInBuffer() const;
float getMaxInBuffer() const;
float getSum() const { return _sum; };
// return true if buffer is full
bool bufferIsFull() const { return _count == _size; };
@ -54,12 +56,13 @@ public:
// use not all elements just a part from 0..partial-1
// (re)setting partial will clear the internal buffer.
void setPartial(const uint16_t partial = 0); // 0 ==> use all
bool setPartial(const uint16_t partial = 0); // 0 ==> use all
uint16_t getPartial() { return _partial; };
// get some stats from the last count additions.
float getAverageLast(uint16_t count);
float getStandardDeviationLast(uint16_t count);
float getMinInBufferLast(uint16_t count);
float getMaxInBufferLast(uint16_t count);

View File

@ -0,0 +1,25 @@
UNO IDE 1.8.19
ra_performance.ino
RUNNINGAVERAGE_LIB_VERSION: 0.4.5
clear : 60
addValue : 24
fillValue : 1512
getValue : 4
getAverage : 520
getFastAverage : 36
getStandardDeviation : 1856
getStandardError : 1920
getMin : 8
getMax : 4
getMinInBuffer : 216
getMaxInBuffer : 208
bufferIsFull : 8
getElement : 4
getSize : 8
getCount : 8
done...

View File

@ -0,0 +1,26 @@
UNO IDE 1.8.19
ra_performance.ino
RUNNINGAVERAGE_LIB_VERSION: 0.4.6
clear : 60
addValue : 24
fillValue : 1520
getValue : 8
getAverage : 552
getFastAverage : 40
getStandardDeviation : 1856
getStandardError : 1920
getMin : 4
getMax : 4
getMinInBuffer : 216
getMaxInBuffer : 212
getSum : 4
bufferIsFull : 12
getElement : 4
getSize : 8
getCount : 8
done...

View File

@ -48,6 +48,7 @@ void setup(void)
test_getMax();
test_getMinInBuffer();
test_getMaxInBuffer();
test_getSum();
test_bufferIsFull();
test_getElement();
@ -191,6 +192,17 @@ void test_getMaxInBuffer()
}
void test_getSum()
{
start = micros();
x = myRA.getSum();
stop = micros();
Serial.print("\tgetSum \t\t: ");
Serial.println(stop - start);
delay(10);
}
void test_bufferIsFull()
{
start = micros();
@ -241,4 +253,3 @@ void loop()
// -- END OF FILE --

View File

@ -21,6 +21,7 @@ getMin KEYWORD2
getMax KEYWORD2
getMinInBuffer KEYWORD2
getMaxInBuffer KEYWORD2
getSum KEYWORD2
bufferIsFull() KEYWORD2
getElement KEYWORD2
@ -31,6 +32,7 @@ setPartial KEYWORD2
getPartial KEYWORD2
getAverageLast KEYWORD2
getStandardDeviationLast KEYWORD2
getMinInBufferLast KEYWORD2
getMaxInBufferLast KEYWORD2

View File

@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/RunningAverage.git"
},
"version": "0.4.5",
"version": "0.4.6",
"license": "MIT",
"frameworks": "*",
"platforms": "*",

View File

@ -1,5 +1,5 @@
name=RunningAverage
version=0.4.5
version=0.4.6
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=The library stores the last N individual values in a circular buffer to calculate the running average.

View File

@ -63,7 +63,7 @@ unittest(test_zero_elements)
}
unittest(test_min_max)
unittest(test_min_max_sum)
{
RunningAverage myRA(10);
myRA.clear();
@ -74,14 +74,17 @@ unittest(test_min_max)
}
float mi = myRA.getMin();
assertEqual(-5, mi);
assertEqualFloat(-5, mi, 0.001);
float ma = myRA.getMax();
assertEqual(5, ma);
assertEqualFloat(5, ma, 0.001);
mi = myRA.getMinInBuffer();
assertEqual(-4, mi);
assertEqualFloat(-4, mi, 0.001);
ma = myRA.getMaxInBuffer();
assertEqual(5, ma);
assertEqualFloat(5, ma, 0.001);
float sum = myRA.getSum();
assertEqualFloat(5, sum, 0.001);
}
@ -94,8 +97,8 @@ unittest(test_buffer_full)
for (int i = 0; i < 9; i++)
{
myRA.addValue(i);
assertFalse(myRA.bufferIsFull());
}
assertFalse(myRA.bufferIsFull());
myRA.addValue(42);
assertTrue(myRA.bufferIsFull());
@ -111,8 +114,8 @@ unittest(test_large)
for (int i = 0; i < 299; i++)
{
myRA.addValue(i);
assertFalse(myRA.bufferIsFull());
}
assertFalse(myRA.bufferIsFull());
myRA.addValue(42);
assertTrue(myRA.bufferIsFull());
@ -151,25 +154,37 @@ unittest(test_last)
{
myRA.addValue(i);
}
fprintf(stderr, "\nPARTIAL: 0\n");
assertNAN(myRA.getMinInBufferLast(0));
assertNAN(myRA.getAverageLast(0));
assertNAN(myRA.getMaxInBufferLast(0));
assertNAN(myRA.getStandardDeviationLast(0));
fprintf(stderr, "\nPARTIAL: 1\n");
assertEqualFloat(999.0, myRA.getMinInBufferLast(1), 0.001);
assertEqualFloat(999.0, myRA.getAverageLast(1), 0.001);
assertEqualFloat(999.0, myRA.getMaxInBufferLast(1), 0.001);
assertEqualFloat(0, myRA.getStandardDeviationLast(1), 0.001);
fprintf(stderr, "\nPARTIAL: 10\n");
assertEqualFloat(990.0, myRA.getMinInBufferLast(10), 0.001);
assertEqualFloat(994.5, myRA.getAverageLast(10), 0.001);
assertEqualFloat(999.0, myRA.getMaxInBufferLast(10), 0.001);
assertEqualFloat(3.02765, myRA.getStandardDeviationLast(10), 0.001);
fprintf(stderr, "\nPARTIAL: 100\n");
assertEqualFloat(900.0, myRA.getMinInBufferLast(100), 0.001);
assertEqualFloat(949.5, myRA.getAverageLast(100), 0.001);
assertEqualFloat(999.0, myRA.getMaxInBufferLast(100), 0.001);
assertEqualFloat(29.0115, myRA.getStandardDeviationLast(100), 0.001);
fprintf(stderr, "\nPARTIAL: 1000\n");
assertEqualFloat(700.0, myRA.getMinInBufferLast(1000), 0.001);
assertEqualFloat(849.5, myRA.getAverageLast(1000), 0.001);
assertEqualFloat(999.0, myRA.getMaxInBufferLast(1000), 0.001);
assertEqualFloat(86.7468, myRA.getStandardDeviationLast(1000), 0.001);
}