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.
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)
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.
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
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;
}
Constructor(char *)
179.999999999
179.59'59"9998
should be 179.59'59"9999 so still some work to do...
+ 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
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?)