Angle library

Wrote an experimental library - Angle - to do some basic math on Angles. An angle can be created and displayed as float or in DMS format = degrees, minutes, seconds, milliseconds.

Angle alpha = 75.5;
Serial.print(alpha); → 75.30’00"000
Serial.print(alpha.toDouble()); → 75.50 // 2 decimals default

Creating an angle in DMS format can be useful on an UNO when working with e.g. GPS coordinates or to keep compass heading. The class uses internally 4 integers to keep precision for the addition and subtraction operators (better than a 32 bit float could). Also comparing angles are using all digits.
An angle has a range from -32768 … 32767 degrees, however it has no error detection on any field. It will normalize input e.g. an angle of 14degr, 75 minutes will become 15 degr, 15 minutes.

The library supports the following functions / operators:

  • access functions for degree, minute, second, thousand
  • print the angle
  • conversion toDouble() - (maps on a float on an UNO)
  • equality operators: == != > >= < <=
  • negation of an angle
  • addition and subtraction of angles (e.g. when working with compass directions)
  • multiply an angle with a double
  • divide an angle by an double
  • divide two angles to detemine the ratio

Code can be found on Github - Arduino/libraries/Angle at master · RobTillaart/Arduino · GitHub -

The library includes only one test sketch that shows all operators in some small tests.
The library is not tested extensively so use at your own risk.

As always remarks and comments incl bug reports are welcome.

ideas to add two convert functions: (for display purposes)

Both do not need the high precision (only degrees and maybe minutes)

toRadian() and toDegree() would be useful methods, when the angle is to be input to sin() or cos() that expect the value to be in radians.

Thanks,

  • toDouble() is in fact toDegree()
    I implemented printable and it is a pity it cannot pass a parameter to select the representation to print.

  • toRadians() would be multiply by a constant factor PI/180.0.
    I will add this one.

As the internal representation is more accurate than a 32 bit float, I have been thinking of implementing sin() and cos() to as sin(a + b) = sin(a).cos(b) + cos(a).sin(b), a = whole degree and b is the decimal part.
As the decimal part is always between 0 and 1 (or better between -0.5 and 0.5. ) sin(b) ~ b (< 0.001) and cos(b) ~ 1 this might give insight in the digits lost. To be investigated.

If 64 bit double is supported the internal representation would be double, and code became simpler. Should add some #ifdef to support that for e.g. ARM.

If 64 bit double is supported the internal representation would be double, and code became simpler. Should add some #ifdef to support that for e.g. ARM.

I found no preprocessor directive to check sizeof(float) == sizeof(double). However the compiler has build-in defines for the number of significant digits of Float and Double. A quick test shows that the following preprocessor code detects if doubles are bigger than floats. Should be sufficient to discriminate between boards that support 64 bit double and 32 bit double.

__DBL_DIG__  == double digits
__FLT_DIG__  == float digits

#if __DBL_DIG__ < __FLT_DIG__
// float < double
#elif __DBL_DIG__ == __FLT_DIG__
// float == double
#endif

updated to version 0.1.02 - previous versions should not be used.

Main changes:

  • add toRadians()
  • add subHelper properly
  • simplified the *= and /= operator code
  • fix compare
  • removed degree sign as it did not work as intended.

As always remarks and comments incl bug reports are welcome.

toDouble() is in fact toDegree()

I was thinking in terms of converting a value in radians back to a value in degrees.

Angle a = 45.0;

float rad = a.toRadian();

// do some manipulation of rad involving sin() and/or cos(), changing rad to get the exact value desired (such as catapult launch angle

a.toDegree(rad);
// Show angle on LCD or Serial Monitor

OK I got the idea.
Will be in the next version.

I have written a PR to fix the printing issue mentioned on the developers forum.

It allows simple formatting:

#include <Angle.h>

void setup() {
  Serial.begin(9600);

  Angle angle( 23, 12, 10, 123 );

  Serial.println( angle );                  // Print the most verbose version ( same as angle.format(T) )
  Serial.println( "-------------------" );
  Serial.println( angle.format(D) );        // Print only degree value.
  Serial.println( angle.format(M) );        // Output includes minutes.
  Serial.println( angle.format(S) );        // Output includes minutes, seconds.
  Serial.println( angle.format(T) );        // Output includes minutes, seconds, thousands.
}

void loop() {}

Thanks Pyro,

I'll check it asap

(Sorry Pyro, did not include your code yet)

Been working on an additional constructor that parses a text string representing the angle as a double. So far it seems to perform well, but it does not support negative angles yet. As it parses the string in parts, it offers a higher precision as 32 bits floats do (e.g. the 'double' constructor). It supports up to 9 decimal digits as that much fit in a 32 bit unsigned long.

Angle::Angle(char * str)
{
    uint32_t yy = 0;
    uint8_t d_cnt = 0;

    char *p = str;
    d = 0;

    // skip leading non-digits; assume +
    while (!isdigit(*p)) p++;

    // parse whole part into degrees;
    while (isdigit(*p))
    {
        d *= 10;
        d += (*p - '0');
        p++;
    }

    // parse decimal part into an uint32_t; max 9 digits
    if (*p != '\0')
    {
        p++;  // skip decimal sign .
        while (isdigit(*p) && d_cnt < 9)
        {
            d_cnt++;
            yy *= 10;
            yy += (*p - '0');
            p++;
        }
    }
    // make sure we have 9 decimal places.
    while (d_cnt < 9)
    {
        d_cnt++;
        yy *= 10;
    }

    // convert float to degrees. 1000000000 ~> 36000000  -> /250 * 9
    yy = yy * 4 / 125 + yy / 250;  // just keeps the math within 32 bits

    // split yy in m, s, tt
    t = yy % 10000UL;
    yy = yy / 10000UL;
    s = yy % 60;
    m = yy / 60;
}
  1. Constructor(char *)
    179.999999999
    179.59'59"9998

should be 179.59'59"9999 so still some work to do...

to be continued...

No worries. If you decide you want it later and get merge conflicts, I can update it.

If you want help or testing create a 'dev' branch and add your current work there.

release the 0.1.05 version - Arduino/libraries/Angle at master · RobTillaart/Arduino · GitHub -

+ merge formatting code of Pyro    - thanks
+ new constructor Angle(char* str) - parses a string to get beyond 32 bit float accuracy
+ thousands -> tenthousands        - seems to be a standard to have 4 decimals for the seconds
+ removed TODO's
+ separator array in printTo()     - easier to change them. Might still need a refactor 
+ fix bug in toDouble              - neg values are now calculated correctly (within possible accuracy)
+ added fromRadians()              - on request
+ updated examples

remarks, bug reports, requests are still welcome

@robtillaart:

There is something very, very wrong with how your library handles negative angles.

Angle operator - () { return Angle(-d, m, s, t); };

What happens when d is zero but m is nonzero?

I'll investigate

update: I see the problem, good catch (bad testing on my side)

update: will take quite a while to fix as it pops up in different places - need to redesign

8++ hours later ....

  • found other "negative" bugs
  • Rewrote the code to handle sign separately.
  • added tests
  • one function went from private to public (sign)

Code does behave OK now for all found bugs. new version will be published later this week.

TODO:

  • add more tests (coming days),
  • clean up the code,
  • refactor,
  • add comments,

robtillaart:
8++ hours later ....

  • found other "negative" bugs
  • Rewrote the code to handle sign separately.
  • added tests
  • one function went from private to public (sign)

There is (for the most part) no need to handle negative angles as a special case. I see at least two other ways of doing this:

Way #1: Keep minutes, seconds, and fractional seconds in the ranges 0~59, 0~59, and 0~9999, respectively. Allow degrees to go negative. Example: -(0°00'00"0001) becomes (-1)°59'59"9999.

Way #2: Keep degrees, minutes, seconds, and fractional seconds in the ranges 0~359, 0~59, 0~59, and 0~9999, respectively. Use a separate variable to count turns, and allow the number of turns to go negative.

If I make any trig functions to work with your library, what should I use as the "radius" / "scaling factor"? (In other words, what should sin 90° be? Should it be 1000000000? Or 1073741824? Or something else?)

release the 0.1.06 version - Arduino/libraries/Angle at master · RobTillaart/Arduino · GitHub -

Note this is a beta version to fix the negative bug above.

remarks, bug reports, requests are welcome

@odometer

think it should be something like ...

Angle d(45);
d.sin() -> 0.707...