From a751d44368b82977ead45f7cebe62f786f084df2 Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Wed, 20 Jan 2021 14:11:45 +0100 Subject: [PATCH] detect add() bug (#4) --- README.md | 3 +- Statistic.cpp | 87 ++++++++++--------- Statistic.h | 45 +++++----- .../statistic_add_overflow.ino | 61 +++++++++++++ library.json | 2 +- library.properties | 2 +- 6 files changed, 134 insertions(+), 66 deletions(-) create mode 100644 examples/statistic_add_overflow/statistic_add_overflow.ino diff --git a/README.md b/README.md index e601532..2b3e35e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ The stability of the formulas is improved by the help of Gil Ross (Thanks!) - **Statistic(bool useStdDev = true)** Constructor, default use the standard deviation functions. Setting this flag to **false** reduces math so slight increase of performance. - **void clear(bool useStdDev = true)** resets all variables. -- **void add(float value)** +- **float add(float value)** (since 0.4.3) returns value actually added to internal sum. +If this is (much) different from what should be added it becomes time to call **clear()** - **uint32_t count()** returns zero if count == zero (of course) - **float sum()** returns zero if count == zero - **float minimum()** returns zero if count == zero diff --git a/Statistic.cpp b/Statistic.cpp index 85d4b1b..d188040 100644 --- a/Statistic.cpp +++ b/Statistic.cpp @@ -2,7 +2,7 @@ // FILE: Statistic.cpp // AUTHOR: Rob dot Tillaart at gmail dot com // modified at 0.3 by Gil Ross at physics dot org -// VERSION: 0.4.2 +// VERSION: 0.4.3 // PURPOSE: Recursive statistical library for Arduino // // NOTE: 2011-01-07 Gill Ross @@ -54,6 +54,7 @@ // Added flag to switch on the use of stdDev runtime. [idea marc.recksiedl] // 0.4.1 2020-06-19 fix library.json // 0.4.2 2021-01-08 add Arduino-CI + unit tests +// 0.4.3 2021-01-20 add() returns how much was actually added. #include "Statistic.h" @@ -67,51 +68,53 @@ Statistic::Statistic(bool useStdDev) void Statistic::clear(bool useStdDev) // useStdDev default true. { - _cnt = 0; - _sum = 0; - _min = 0; - _max = 0; - _useStdDev = useStdDev; - _ssqdif = 0.0; - // note not _ssq but sum of square differences - // which is SUM(from i = 1 to N) of f(i)-_ave_N)**2 + _cnt = 0; + _sum = 0; + _min = 0; + _max = 0; + _useStdDev = useStdDev; + _ssqdif = 0.0; + // note not _ssq but sum of square differences + // which is SUM(from i = 1 to N) of f(i)-_ave_N)**2 } // adds a new value to the data-set -void Statistic::add(const float value) +float Statistic::add(const float value) { - if (_cnt == 0) - { - _min = value; - _max = value; - } else { - if (value < _min) _min = value; - else if (value > _max) _max = value; - } - _sum += value; - _cnt++; + float previousSum = _sum; + if (_cnt == 0) + { + _min = value; + _max = value; + } else { + if (value < _min) _min = value; + else if (value > _max) _max = value; + } + _sum += value; + _cnt++; - if (_useStdDev && (_cnt > 1)) - { - float _store = (_sum / _cnt - value); - _ssqdif = _ssqdif + _cnt * _store * _store / (_cnt - 1); + if (_useStdDev && (_cnt > 1)) + { + float _store = (_sum / _cnt - value); + _ssqdif = _ssqdif + _cnt * _store * _store / (_cnt - 1); - // ~10% faster but limits the amount of samples to 65K as _cnt*_cnt overflows - // float _store = _sum - _cnt * value; - // _ssqdif = _ssqdif + _store * _store / (_cnt*_cnt - _cnt); - // - // solution: TODO verify - // _ssqdif = _ssqdif + (_store * _store / _cnt) / (_cnt - 1); - } + // ~10% faster but limits the amount of samples to 65K as _cnt*_cnt overflows + // float _store = _sum - _cnt * value; + // _ssqdif = _ssqdif + _store * _store / (_cnt*_cnt - _cnt); + // + // solution: TODO verify + // _ssqdif = _ssqdif + (_store * _store / _cnt) / (_cnt - 1); + } + return _sum - previousSum; } // returns the average of the data-set added sofar float Statistic::average() const { - if (_cnt == 0) return NAN; // prevent DIV0 error - return _sum / _cnt; + if (_cnt == 0) return NAN; // prevent DIV0 error + return _sum / _cnt; } @@ -119,25 +122,25 @@ float Statistic::average() const // http://www.suite101.com/content/how-is-standard-deviation-used-a99084 float Statistic::variance() const { - if (!_useStdDev) return NAN; - if (_cnt == 0) return NAN; // prevent DIV0 error - return _ssqdif / _cnt; + if (!_useStdDev) return NAN; + if (_cnt == 0) return NAN; // prevent DIV0 error + return _ssqdif / _cnt; } float Statistic::pop_stdev() const { - if (!_useStdDev) return NAN; - if (_cnt == 0) return NAN; // prevent DIV0 error - return sqrt( _ssqdif / _cnt); + if (!_useStdDev) return NAN; + if (_cnt == 0) return NAN; // prevent DIV0 error + return sqrt( _ssqdif / _cnt); } float Statistic::unbiased_stdev() const { - if (!_useStdDev) return NAN; - if (_cnt < 2) return NAN; // prevent DIV0 error - return sqrt( _ssqdif / (_cnt - 1)); + if (!_useStdDev) return NAN; + if (_cnt < 2) return NAN; // prevent DIV0 error + return sqrt( _ssqdif / (_cnt - 1)); } // -- END OF FILE -- diff --git a/Statistic.h b/Statistic.h index 75fe1ef..ae34cb4 100644 --- a/Statistic.h +++ b/Statistic.h @@ -3,7 +3,7 @@ // FILE: Statistic.h // AUTHOR: Rob dot Tillaart at gmail dot com // modified at 0.3 by Gil Ross at physics dot org -// VERSION: 0.4.2 +// VERSION: 0.4.3 // PURPOSE: Recursive Statistical library for Arduino // HISTORY: See Statistic.cpp // @@ -13,34 +13,37 @@ #include -#define STATISTIC_LIB_VERSION (F("0.4.2")) +#define STATISTIC_LIB_VERSION (F("0.4.3")) class Statistic { public: - Statistic(bool useStdDev = true); // "switches on/off" stdev run time - void clear(bool useStdDev = true); // "switches on/off" stdev run time - void add(const float); + Statistic(bool useStdDev = true); // "switches on/off" stdev run time + void clear(bool useStdDev = true); // "switches on/off" stdev run time + float add(const float); // returns value actually added - // returns the number of values added - uint32_t count() const { return _cnt; }; // zero if count == zero - float sum() const { return _sum; }; // zero if count == zero - float minimum() const { return _min; }; // zero if count == zero - float maximum() const { return _max; }; // zero if count == zero - float average() const; // NAN if count == zero - // useStdDev must be true to use next three - float variance() const; // NAN if count == zero - float pop_stdev() const; // population stdev // NAN if count == zero - float unbiased_stdev() const; // NAN if count == zero + // returns the number of values added + uint32_t count() const { return _cnt; }; // zero if count == zero + float sum() const { return _sum; }; // zero if count == zero + float minimum() const { return _min; }; // zero if count == zero + float maximum() const { return _max; }; // zero if count == zero + float average() const; // NAN if count == zero + + + // useStdDev must be true to use next three + float variance() const; // NAN if count == zero + float pop_stdev() const; // population stdev // NAN if count == zero + float unbiased_stdev() const; // NAN if count == zero + protected: - uint32_t _cnt; - float _sum; - float _min; - float _max; - bool _useStdDev; - float _ssqdif; // sum of squares difference + uint32_t _cnt; + float _sum; + float _min; + float _max; + bool _useStdDev; + float _ssqdif; // sum of squares difference }; diff --git a/examples/statistic_add_overflow/statistic_add_overflow.ino b/examples/statistic_add_overflow/statistic_add_overflow.ino new file mode 100644 index 0000000..7aafe38 --- /dev/null +++ b/examples/statistic_add_overflow/statistic_add_overflow.ino @@ -0,0 +1,61 @@ +// +// FILE: TimingTest.ino +// AUTHOR: Rob dot Tillaart at gmail dot com +// VERSION: 0.2.0 +// PURPOSE: this sketch shows a known problem when +// internal sum is orders of magnitude larger +// than the added value. + +#include "Statistic.h" + +Statistic myStats; + +void setup(void) +{ + Serial.begin(115200); + Serial.println(__FILE__); + Serial.print("STATISTIC_LIB_VERSION: "); + Serial.println(STATISTIC_LIB_VERSION); + + myStats.clear(); + + Serial.println("\nCOUNT\tVALUE\tACTUAL\tRATIO"); + for (float value = 1e8; value > 1; value *= 0.1) + { + float actual = myStats.add(value); + float ratio = actual / value; + + Serial.print(myStats.count()); + Serial.print('\t'); + Serial.print(value); + Serial.print('\t'); + Serial.print(actual); + Serial.print('\t'); + Serial.print(ratio); + Serial.print('\n'); + } + + for (float value = 10; value > 0.1; value -= 1) + { + float actual = myStats.add(value); + float ratio = actual / value; + + Serial.print(myStats.count()); + Serial.print('\t'); + Serial.print(value); + Serial.print('\t'); + Serial.print(actual); + Serial.print('\t'); + Serial.print(ratio); + Serial.print('\n'); + } + + Serial.print("\nQED..."); +} + +void loop(void) +{ + +} + +// -- END OF FILE -- diff --git a/library.json b/library.json index 5bb1a62..4392bd7 100644 --- a/library.json +++ b/library.json @@ -18,7 +18,7 @@ "type": "git", "url": "https://github.com/RobTillaart/Statistic.git" }, - "version":"0.4.2", + "version":"0.4.3", "frameworks": "arduino", "platforms": "*" } diff --git a/library.properties b/library.properties index b4ab8db..0430612 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Statistic -version=0.4.2 +version=0.4.3 author=Rob Tillaart maintainer=Rob Tillaart sentence=Library with basic statistical functions for Arduino.