diff --git a/libraries/RunningAverage/CHANGELOG.md b/libraries/RunningAverage/CHANGELOG.md index 8dab2545..6357aeb4 100644 --- a/libraries/RunningAverage/CHANGELOG.md +++ b/libraries/RunningAverage/CHANGELOG.md @@ -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 @@ -107,7 +119,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.2.01] - 2012-11-21 - refactored -## [0.2.00] - 2012-??-?? +## [0.2.00] - 2012-??-?? - Yuval Naveh added trimValue (found on web) - http://stromputer.googlecode.com/svn-history/r74/trunk/Arduino/Libraries/RunningAverage/RunningAverage.cpp diff --git a/libraries/RunningAverage/README.md b/libraries/RunningAverage/README.md index f55b3f50..e7bbfc20 100644 --- a/libraries/RunningAverage/README.md +++ b/libraries/RunningAverage/README.md @@ -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 @@ -129,8 +165,7 @@ Get the average of subset - count elements from start. See examples -## Future - +## Future #### Must @@ -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 diff --git a/libraries/RunningAverage/RunningAverage.cpp b/libraries/RunningAverage/RunningAverage.cpp index 268dde4f..795dcb43 100644 --- a/libraries/RunningAverage/RunningAverage.cpp +++ b/libraries/RunningAverage/RunningAverage.cpp @@ -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; } diff --git a/libraries/RunningAverage/RunningAverage.h b/libraries/RunningAverage/RunningAverage.h index 94585de0..dd244cad 100644 --- a/libraries/RunningAverage/RunningAverage.h +++ b/libraries/RunningAverage/RunningAverage.h @@ -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); diff --git a/libraries/RunningAverage/performance.txt b/libraries/RunningAverage/examples/ra_performance/performance_0.3.0.txt similarity index 100% rename from libraries/RunningAverage/performance.txt rename to libraries/RunningAverage/examples/ra_performance/performance_0.3.0.txt diff --git a/libraries/RunningAverage/examples/ra_performance/performance_0.4.5.txt b/libraries/RunningAverage/examples/ra_performance/performance_0.4.5.txt new file mode 100644 index 00000000..ad54f9c7 --- /dev/null +++ b/libraries/RunningAverage/examples/ra_performance/performance_0.4.5.txt @@ -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... diff --git a/libraries/RunningAverage/examples/ra_performance/performance_0.4.6.txt b/libraries/RunningAverage/examples/ra_performance/performance_0.4.6.txt new file mode 100644 index 00000000..7612a9f7 --- /dev/null +++ b/libraries/RunningAverage/examples/ra_performance/performance_0.4.6.txt @@ -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... diff --git a/libraries/RunningAverage/examples/ra_performance/ra_performance.ino b/libraries/RunningAverage/examples/ra_performance/ra_performance.ino index 0f17bd8a..53c4db78 100644 --- a/libraries/RunningAverage/examples/ra_performance/ra_performance.ino +++ b/libraries/RunningAverage/examples/ra_performance/ra_performance.ino @@ -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 -- - diff --git a/libraries/RunningAverage/keywords.txt b/libraries/RunningAverage/keywords.txt index 222a9f3d..f15bef13 100644 --- a/libraries/RunningAverage/keywords.txt +++ b/libraries/RunningAverage/keywords.txt @@ -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 diff --git a/libraries/RunningAverage/library.json b/libraries/RunningAverage/library.json index b15057d4..e01570e6 100644 --- a/libraries/RunningAverage/library.json +++ b/libraries/RunningAverage/library.json @@ -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": "*", diff --git a/libraries/RunningAverage/library.properties b/libraries/RunningAverage/library.properties index 25baca87..64fc2f91 100644 --- a/libraries/RunningAverage/library.properties +++ b/libraries/RunningAverage/library.properties @@ -1,5 +1,5 @@ name=RunningAverage -version=0.4.5 +version=0.4.6 author=Rob Tillaart maintainer=Rob Tillaart sentence=The library stores the last N individual values in a circular buffer to calculate the running average. diff --git a/libraries/RunningAverage/test/unit_test_001.cpp b/libraries/RunningAverage/test/unit_test_001.cpp index 97cfdbd2..f907799e 100644 --- a/libraries/RunningAverage/test/unit_test_001.cpp +++ b/libraries/RunningAverage/test/unit_test_001.cpp @@ -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); + }