It is more reliable because it is impossible to provide the wrong argument to a trigonometric function. I'm sure any reader who has written programs involving Sines and Cosines has, at least once, tried to take the sine of an angle expressed in degrees using a function expecting an input in radians, or vice-versa. An even more common mistake is multiplying by PI_OVER_180 when the angle should have been divided by that factor, or vice-versa. Those days are gone forever if you use the TRIG package.
These are all good reasons for leaving the math routines out of the Ada language. I wouldn't have it any other way. Compiler vendors, however, wisely recognized that most of us programmers use math functions often in our application programs. Furthermore, we want to solve the problems we are being paid to solve, not reinvent the Sine function. We would abuse their sales representatives if they didn't include a math library as part of the programming environment, so most compilers today come with a math library. You will probably find a package called something like MATH_LIB or GENERAL_MATH in the system library.
Math libraries often cause portability problems. They generally have different names for the library as well as the functions. For example, the square root function might be called Sqr or Sqrt. Some of math libraries are generics and some have already been instantiated. Some math libraries have two functions, Sin and Sind, for taking sines in radians and degrees. Other libraries only have a radian sine function. Suppose your program used Sind to find the sine of angles expressed in degrees, and you transported it to another system that used Sind for a double-precision sine in radians. How long would it take you to find that bug?
I run into these problems a lot because I use so many different Ada compilers. I solved the problem by using the TRIG package as a shell over the underlying math library. Once I get the TRIG package ported to a new system, then all my other programs can be transported without any math modifications.
The TRIG package specification remains the same on every system, but the body has to be specially tailored for each system. Listing 8 shows the TRIG package body for the DEC compiler. It simply calls the corresponding VAX/VMS math library routine for each function. The TRIG package body for the Meridian compiler in Listing 9 uses a slightly different approach, just for the sake of illustrating a different way of doing it. (Mathematicians know the method I used in the Meridian body is inferior, and we will talk about that much later when I show you how the routine was tested.) I pretended that there wasn't any Cos or Tan function available and derived them from the Sin function. (If you are using an embedded computer, you might just have a sine lookup table, or a coprocessor that only computes sines.) In the fall of 1987, Alsys did not officially provide a math library. The Version 3.2 distribution disk contained a math library contributed by a customer. Listing 10 uses that unofficial Alsys math library. No matter what the package body implementation is, the package specification doesn't change, and that makes all the programs that use the TRIG package portable.
This TRIG package may not exactly fit your needs, but it is built in such a way that you can modify it to do exactly what you want. You can derived Deg and Rad from any floating point type you desire. You can change the body to use any desired algorithm. Since it isn't part of the language you are free to do whatever you want.
The first special case is the tangent of 90 degrees. The theoretical value is infinite. DEC Ada raises FLOAT_MATH_LIB.FLOOVEMAT. I could have handled this by simply raising NUMERIC_ERROR in its place and let the application program figure out how to handle it, but instead I decided to return a very big number. The problem is, how big is "very big?" I decided that since I had previously decided limits for DIM_FLOAT, I might as well use the same maximum and minimum here. I return DIM_FLOAT.Last for the Tangent of 90 degrees, and DIM_FLOAT.First for the Tangent of 270 degrees. If that's not appropriate for your application, feel free to change it.
The second special case is the two argument arctangent where both arguments are zero. What is the bearing from the origin to the origin? It is undefined. You could say that it is a stupid question that doesn't deserved to be answered, but consider this: People who are trying to drop bombs on you generally try to fly right over your head, and sometimes they succeed. When that happens the elevation is 90 degrees and the azimuth is Atan(0.0,0.0). It could be an important moment in your life, and you want the right answer. If you are flying an airplane and pull a vertical loop, there are moments when you are flying straight up or straight down, so your heading is Atan(0.0,0.0).
My first approach to the problem was to define Atan(0.0,0.0) to be 0.0 because I wanted to avoid raising an exception. That drove an aircraft simulation nuts when it tried to simulate a loop if the aircraft wasn't flying due north or due south. I decided it was better to raise the INVALID_ARGUMENT exception and let the application program handle it. (The application program handled it by using the previous heading, and that worked fine.) Your situation may require a different solution, and you are free to do whatever you like with the source code.
Suppose a position is expressed in polar coordinates. The distance from the origin is 10 and the angle is 30 degrees. If we need to convert this to cartesian coordinates, we compute X = 10 * Cos(30) = 8.66. Then we find Y = 10 * Sin(30) = 5.00. No problem. But suppose we want to convert that point back to polar coordinates. Do we use Atan(8.66,5.00) or Atan(5.00,8.66)?
The common convention is that Atan(A1, A2) returns the arctangent of A1/A2, so the first argument should be the Y component and the second argument should be the X component. So, if the X coordinate is 8.66 and the Y coordinate is 5.00, the correct angle is found by computing Atan(5.00,8.66).
Now, what is the bearing of a target 8.66 miles East and 5.00 miles North? You think it is Atan(5.00,8.66)? Think again! Atan(5.00,8.66) yields 30 degrees, but the correct answer is 60 degrees.
Mathematicians compute the angle of a point in polar coordinates counterclockwise from the horizontal axis, as shown in Figure 12. Anyone who has ever used a compass (especially pilots and radar operators) knows 0 degrees is North and 90 degrees is East. Angles are measured clockwise from the vertical axis, as shown in Figure 13. Therefore, the bearing to a target 8.66 miles East and 5.00 miles North is given by Atan(8.66,5.00).
That why I called the arguments to the TRIG.Atan function EAST_OR_Y and NORTH_OR_X. I makes it easier for me to remember the correct parameter associations.
Things get even worse in three dimensions because there are so many right-handed coordinate systems. Mathematicians like to use X (right), Y (ahead), and Z (up) vectors. Airborne systems like to use NORTH, EAST, DOWN. Ground-based systems like to use EAST, NORTH, UP. Body-referenced coordinates can be X',Y',Z' or X",Y",Z" where the major body axis could be aligned with any of those vectors, depending on the programmer's whim.
Fortunately Ada allows you to define enumeration types that specify body directions in meaningful terms. If you want to use an array to represent a three dimensional velocity in body coordinates you can do this: