0.2.0 AverageAngle

This commit is contained in:
rob tillaart 2023-02-01 13:53:15 +01:00
parent f5feb9e053
commit bbd3240c47
12 changed files with 168 additions and 34 deletions

View File

@ -6,7 +6,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: arduino/arduino-lint-action@v1
with:
library-manager: update

View File

@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6

View File

@ -10,7 +10,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: json-syntax-check
uses: limitusus/json-syntax-check@v1
with:

View File

@ -1,7 +1,7 @@
//
// FILE: AverageAngle.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.1.9
// VERSION: 0.2.0
// DATE: 2017-11-21
// PURPOSE: class for averaging angles
// URL: https://github.com/RobTillaart/AverageAngle
@ -10,6 +10,9 @@
#include "AverageAngle.h"
const float AA_OVERFLOW_THRESHOLD = 10000;
AverageAngle::AverageAngle(const enum AngleType type)
{
_type = type;
@ -27,8 +30,20 @@ uint32_t AverageAngle::add(float alpha, float length)
{
alpha *= GRAD_TO_RAD; // (PI / 200.0);
}
_sumx += (cos(alpha) * length);
_sumy += (sin(alpha) * length);
float dx = cos(alpha);
float dy = sin(alpha);
if (length != 1.0)
{
dx *= length;
dy *= length;
}
_sumx += dx;
_sumy += dy;
_error = AVERAGE_ANGLE_OK;
if ((abs(_sumx) > AA_OVERFLOW_THRESHOLD) || (abs(_sumy) > AA_OVERFLOW_THRESHOLD))
{
_error = AVERAGE_ANGLE_OVERFLOW;
}
_count++;
return _count;
}
@ -39,13 +54,14 @@ void AverageAngle::reset()
_sumx = 0;
_sumy = 0;
_count = 0;
_error = AVERAGE_ANGLE_OK;
}
uint32_t AverageAngle::count()
{
return _count;
};
}
float AverageAngle::getAverage()
@ -60,6 +76,16 @@ float AverageAngle::getAverage()
{
angle *= RAD_TO_GRAD; // (200.0 / PI);
}
// error reporting
if (isnan(angle))
{
_error = AVERAGE_ANGLE_SINGULARITY;
}
else
{
_error = AVERAGE_ANGLE_OK;
}
return angle;
}
@ -92,5 +118,13 @@ bool AverageAngle::setType(const enum AngleType type)
}
int AverageAngle::lastError()
{
int e = _error;
_error = AVERAGE_ANGLE_OK;
return e;
}
// -- END OF FILE --

View File

@ -2,7 +2,7 @@
//
// FILE: AverageAngle.h
// AUTHOR: Rob Tillaart
// VERSION: 0.1.9
// VERSION: 0.2.0
// DATE: 2017-11-21
// PURPOSE: class for averaging angles.
@ -10,12 +10,17 @@
#include "math.h"
#include "Arduino.h"
#define AVERAGE_ANGLE_LIB_VERSION (F("0.1.9"))
#define AVERAGE_ANGLE_LIB_VERSION (F("0.2.0"))
#define GRAD_TO_RAD (PI / 200.0)
#define RAD_TO_GRAD (200.0 / PI)
#define AVERAGE_ANGLE_OK 0
#define AVERAGE_ANGLE_OVERFLOW -10
#define AVERAGE_ANGLE_SINGULARITY -20
class AverageAngle
{
public:
@ -39,12 +44,14 @@ public:
AngleType type();
bool setType(AngleType type);
int lastError();
private:
AngleType _type;
float _sumx;
float _sumy;
uint32_t _count;
int _error;
};

View File

@ -6,10 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.2.0] - 2023-02-01
- add experimental OVERFLOW detection in **add()**
- add **lastError()**
- OK, OVERFLOW, SINGULARITY
- update GitHub actions
- update license 2023
- update readme.md
- update keywords.txt
----
## [0.1.9] - 2022-12-07
- fix #7 update readme.md
-
## [0.1.8] - 2022-10-29
- add RP2040 support to build-CI.

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2022 Rob Tillaart
Copyright (c) 2017-2023 Rob Tillaart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -28,9 +28,11 @@ The average angle is calculated by converting the added angle (in DEGREES, etc)
These (x,y) are added to a (sumX, sumY) and divided by the number of angles added so far.
#### Related to
#### Related
- https://github.com/RobTillaart/Angle
- https://github.com/RobTillaart/AngleConvertor
- https://github.com/RobTillaart/AverageAngle
- https://github.com/RobTillaart/runningAngle
@ -47,22 +49,67 @@ These (x,y) are added to a (sumX, sumY) and divided by the number of angles adde
## Interface
- **AverageAngle(AngleType type = DEGREES)** constructor defaults to degrees.
```cpp
#include "AverageAngle.h"
```
#### Constructor
- **AverageAngle(AngleType type = DEGREES)** constructor, defaults to degrees.
- **AngleType type()** returns DEGREES, RADIANS or GRADIANS.
- **void setType(AngleType type)** changes type DEGREES, RADIANS or GRADIANS.
Type can be changed run time and still continue to add.
- **void reset()** clears internal counters.
#### Core
- **uint32_t add(float alpha, float length = 1.0)** add a new angle,
optional with length other than 1.
Returns the number of elements (count).
- **void reset()** clears internal counters.
- **uint32_t count()** the amount of angles added.
If the internal sumx or sumy is >= 10000, the error **AVERAGE_ANGLE_OVERFLOW** is set.
This indicates that the internal math is near or over its accuracy limits.
- **uint32_t count()** the number of angles added.
If count == 0, there is no average.
- **float getAverage()** returns the average, unless count == 0
or the internal sums == (0,0) in which case we have a singularity ==> NAN (not a number)
If NAN the error **AVERAGE_ANGLE_SINGULARITY** is set.
- **float getTotalLength()** the length of the resulting 'angle' when we see them as vectors.
If count == 0 ==> total length = 0.
- **float getAverageLength()** returns the average length of the angles added.
If count == 0 ==> average length = 0.
- **AngleType type()** returns DEGREES, RADIANS or GRADIANS.
- **void setType(AngleType type)** changes type DEGREES, RADIANS or GRADIANS.
Type can be changed run time and still continue to add.
#### Error handling
- **int lastError()** return the last error detected.
| name | value |
|:----------------------------|:-------:|
| AVERAGE_ANGLE_OK | 0 |
| AVERAGE_ANGLE_OVERFLOW | -10 |
| AVERAGE_ANGLE_SINGULARITY | -20 |
#### Experimental Overflow
(since 0.2.0)
When the internal sumx or sumy is large (> 10000) the accuracy of the addition
becomes critical, leading to serious errors in the average and length functions.
To detect this the function **add()** sets the error **AVERAGE_ANGLE_OVERFLOW**.
This error can be checked with **lastError()**.
The function **add()** will add the new angle as good as possible.
Note this condition is independent of the **AngleType** as the internal math
uses radians. The condition will be triggered faster when the length parameter
is used.
The overflow threshold of 10000 can be patched in the .cpp file if needed.
As this feature is **experimental**, the trigger condition for overflow will
probably be redesigned in the future. See future section below.
## Gradians
@ -72,6 +119,8 @@ There are 100 gradians in a right angle. A full circle = 400 gradians.
https://en.wikipedia.org/wiki/Gradian
See also AngleConvertor library.
## Operation
@ -111,22 +160,36 @@ just change the type runtime.
Serial.println(AA.getAverage());
```
#### Warning
As the internal representation has a limited resolution (float) it can occur that when
the internal values are large, one cannot add a very small angle any more.
Did not encounter it yet myself.
## Future
#### Must
- investigate if and how the internal math can be made more robust against overflow.
- use double iso float (will work on certain platforms) (must)
- uint32_t?
- accuracy threshold depends on float/double usage. (sizeof(double)==8)
- threshold depends on the units of length.
if all add's are using 10000 as length they have equal weight.
normalizing the weight? how? user responsibility?
- get set threshold via API?
- use of threshold versus error detection (sum - angle == previous or not)
- split OVERFLOW error in X and Y
#### Should
- add performance example
- add overflow example
- add singularity example
#### Could
- add a USER AngleType, in which the user can map 0..360 degrees to any range.
- float userFactor = 1.0; (default)
- can even be negative?
- use cases? e.g 0..4 quadrant?
- maybe better for the AngleConvertor class.
#### Wont

View File

@ -14,10 +14,19 @@ getAverage KEYWORD2
getTotalLength KEYWORD2
getAverageLength KEYWORD2
setType KEYWORD2
lastError KEYWORD2
# Constants (LITERAL1)
AVERAGE_ANGLE_LIB_VERSION LITERAL1
DEGREES LITERAL1
RADIANS LITERAL1
GRADIANS LITERAL1
AVERAGE_ANGLE_OK LITERAL1
AVERAGE_ANGLE_OVERFLOW LITERAL1
AVERAGE_ANGLE_SINGULARITY LITERAL1

View File

@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/AverageAngle.git"
},
"version": "0.1.9",
"version": "0.2.0",
"license": "MIT",
"frameworks": "arduino",
"platforms": "*",

View File

@ -1,5 +1,5 @@
name=AverageAngle
version=0.1.9
version=0.2.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Library to average angles correctly around 0.

View File

@ -30,6 +30,7 @@ unittest_setup()
fprintf(stderr, "AVERAGE_ANGLE_LIB_VERSION: %s\n", (char*) AVERAGE_ANGLE_LIB_VERSION);
}
unittest_teardown()
{
}
@ -41,6 +42,11 @@ unittest(test_constants)
assertEqualFloat(180.0 / PI, RAD_TO_DEG, 0.00001);
assertEqualFloat(PI / 200.0, GRAD_TO_RAD, 0.00001);
assertEqualFloat(200.0 / PI, RAD_TO_GRAD, 0.00001);
// ERROR CODES
assertEqual(0, AVERAGE_ANGLE_OK);
assertEqual(-10, AVERAGE_ANGLE_OVERFLOW);
assertEqual(-20, AVERAGE_ANGLE_SINGULARITY);
}
@ -53,6 +59,10 @@ unittest(test_constructor)
assertEqual(AverageAngle::DEGREES, dd.type());
assertEqual(AverageAngle::RADIANS, rr.type());
assertEqual(AverageAngle::GRADIANS, gg.type());
assertEqual(AVERAGE_ANGLE_OK, dd.lastError());
assertEqual(AVERAGE_ANGLE_OK, rr.lastError());
assertEqual(AVERAGE_ANGLE_OK, gg.lastError());
}
@ -118,4 +128,6 @@ unittest(test_gradians)
unittest_main()
// --------
// -- END OF FILE --