View Javadoc
1   /*
2    * Copyright (C) 2018 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.navigation.geodesic;
17  
18  /**
19   * Defines mathematical functions and constants.
20   * Based on net.sf.geographiclib library.
21   */
22  public class GeoMath {
23  
24      /**
25       * Number of binary digits in the fraction of double precision number.
26       * This is equivalent to C++'s {@code numeric_limits<double>::digits}.
27       */
28      public static final int DIGITS = 53;
29  
30      /**
31       * Equivalent to C++'s {@code numeric_limits<double>::epsilon()}. This is equal to
32       * 0.5^(DIGITS - 1).
33       */
34      public static final double EPSILON = Math.ulp(1.0);
35  
36      /**
37       * Equivalent to C++'s {@code numeric_limits<double>::min()}. This is equal to 0.5^1022.
38       */
39      public static final double MIN = Double.MIN_NORMAL;
40  
41      /**
42       * Constructor.
43       * Prevents instantiation.
44       */
45      private GeoMath() {
46      }
47  
48      /**
49       * Square a number.
50       *
51       * @param x the argument.
52       * @return <i>x</i><sup>2</sup>.
53       */
54      public static double sq(final double x) {
55          return x * x;
56      }
57  
58      /**
59       * The hypotenuse function avoiding underflow and overflow. This is equivalent
60       * to {@link Math#hypot(double, double)}.
61       *
62       * @param x the first argument.
63       * @param y the second argument.
64       * @return sqrt(<i>x</i><sup>2</sup> + <i>y</i><sup>2</sup>).
65       */
66      public static double hypot(double x, double y) {
67          x = Math.abs(x);
68          y = Math.abs(y);
69  
70          final var a = Math.max(x, y);
71          final var b = Math.min(x, y) / (a != 0 ? a : 1);
72          return a * Math.sqrt(1 + b * b);
73          // For an alternative method see
74          // C. Moler and D. Morrisin (1983) https://doi.org/10.1147/rd.276.0577
75          // and A. A. Dubrulle (1983) https://doi.org/10.1147/rd.276.0582
76      }
77  
78      /**
79       * log(1 + <i>x</i>) accurate near <i>x</i> = 0. This is equivalent to {@link Math#log1p(double)}.
80       * <p>
81       * This is taken from D.Goldberg,
82       * <a href="https://doi.org/10.1145/103162.103163">What every computer scientist should
83       * know about floating-point arithmetic</a> (1991),
84       * Theorem 4. See also, N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd Edition
85       * (SIAM, 2002), Answer to Problem 1.5, p 528.
86       * </p>
87       *
88       * @param x the argument.
89       * @return log(1 + <i>x</i>).
90       */
91      public static double log1p(final double x) {
92          final var y = 1 + x;
93          final var z = y - 1;
94          // Here's the explanation for this magic: y = 1 + z, exactly, and z approx x, thus log(y)/z
95          // (which is nearly constant near z = 0) returns a good approximation to the true log(1 + x)/x.
96          // The multiplication of x * (log(y)/z) introduces little additional error.
97          return z == 0 ? x : x * Math.log(y) / z;
98      }
99  
100     /**
101      * The inverse hyperbolic tangent function. This is defined in terms of {@link GeoMath#log1p(double)} in order
102      * to maintain accuracy near <i>x</i> = 0.
103      * In addition, the odd parity of the function is enforced.
104      *
105      * @param x the argument.
106      * @return atanh(<i>x</i>).
107      */
108     public static double atanh(final double x) {
109         // Enforce odd parity
110         var y = Math.abs(x);
111         y = Math.log1p(2 * y / (1 - y)) / 2;
112         return x < 0 ? -y : y;
113     }
114 
115     /**
116      * Copy the sign. This is equivalent to {@link Math#copySign(double, double)}
117      *
118      * @param x gives the magnitude of the result.
119      * @param y gives the sign of the result.
120      * @return value with the magnitude of <i>x</i> and with the sign of <i>y</i>.
121      */
122     public static double copysign(final double x, final double y) {
123         return Math.abs(x) * (y < 0 || y == 0 ? -1 : 1);
124     }
125 
126     /**
127      * The cube root function. This is equivalent to {@link Math#cbrt(double)}.
128      *
129      * @param x the argument.
130      * @return the real cube root of <i>x</i>.
131      */
132     public static double cbrt(final double x) {
133         // Return the real cube root
134         final var y = Math.pow(Math.abs(x), 1 / 3.0);
135         return x < 0 ? -y : y;
136     }
137 
138     /**
139      * Normalizes sinus and cosine.
140      *
141      * @param sinx sinus of x.
142      * @param cosx cosine of x.
143      * @return normalized values.
144      * @throws IllegalArgumentException if provided sinus and cosine values have zero
145      *                                  norm.
146      */
147     public static Pair norm(final double sinx, final double cosx) {
148         final var r = hypot(sinx, cosx);
149         if (r == 0.0) {
150             throw new IllegalArgumentException();
151         }
152 
153         return new Pair(sinx / r, cosx / r);
154     }
155 
156     /**
157      * The error-free sum of two numbers.
158      * See D.E. Knuth, TAOCP, Vol 2, 4.2.2, Theorem B.
159      *
160      * @param u the first number in the sum.
161      * @param v the second number in the sum.
162      * @return Pair(<i>s</i>, <i>t</i>) with <i>s</i> = round(<i>u</i> +
163      * <i>v</i>) and <i>t</i> = <i>u</i> + <i>v</i> - <i>s</i>.
164      */
165     public static Pair sum(final double u, final double v) {
166         final var s = u + v;
167         var up = s - v;
168         var vpp = s - up;
169         up -= u;
170         vpp -= v;
171         final var t = -(up + vpp);
172         // u + v = s + t = round(u + v) + t
173         return new Pair(s, t);
174     }
175 
176     /**
177      * Evaluate a polynomial.
178      * <p>
179      * Evaluate <i>y</i> = &sum;<sub><i>n</i>=0..<i>n</i></sub>
180      * <i>p</i><sub><i>s</i>+<i>n</i></sub>
181      * <i>x</i><sup><i>n</i>&minus;<i>n</i></sup>.
182      * Return 0 if <i>n</i> &lt; 0.
183      * Return <i>p</i><sub><i>s</i></sub>, if <i>n</i> = 0 (even if <i>x</i> is
184      * infinite or a nan). The evaluation uses Horner's method.
185      * This is equivalent to {@link com.irurueta.numerical.polynomials.Polynomial#evaluate(double)}.
186      *
187      * @param n the order of the polynomial.
188      * @param p the coefficient array (of size <i>n</i> + <i>s</i> + 1 or more).
189      * @param s starting index of the array.
190      * @param x the variable.
191      * @return the value of the polynomial.
192      */
193     public static double polyval(int n, final double[] p, int s, final double x) {
194         var y = n < 0 ? 0 : p[s++];
195         while (--n >= 0) {
196             y = y * x + p[s++];
197         }
198         return y;
199     }
200 
201     /**
202      * Makes the smallest gap in x = 1 / 16 - nextafter(1/16, 0) = 1/2^57 for reals = 0.7 pm on the earth if x is an
203      * angle in degrees. (This is about 1000 times more resolution than we get with angles around 90 degrees.). We use
204      * this to avoid having to deal with near singular cases when x is non-zero but tiny (e.g. 1.0e-200). This converts
205      * -0 to +0; however tiny negative numbers get converted to -0.
206      *
207      * @param x value to be converted
208      * @return rounded value.
209      */
210     public static double angRound(final double x) {
211         final var z = 1 / 16.0;
212         if (x == 0) {
213             return 0;
214         }
215 
216         var y = Math.abs(x);
217         // The compiler mustn't "simplify" z - (z - y) to y
218         y = y < z ? z - (z - y) : y;
219         return x < 0 ? -y : y;
220     }
221 
222     /**
223      * Normalizes an angle (restricted input range).
224      * The range of <i>x</i> is unrestricted.
225      *
226      * @param x the angle in degrees.
227      * @return the angle reduced to the range [&minus;180&deg;, 180&deg;).
228      */
229     public static double angNormalize(double x) {
230         x = x % 360.0;
231         if (x <= -180) {
232             return x + 360;
233         } else {
234             return x <= 180 ? x : x - 360;
235         }
236     }
237 
238     /**
239      * Normalizes latitude.
240      *
241      * @param x the angle in degrees.
242      * @return x if it is in the range [&minus;90&deg;, 90&deg;], otherwise return NaN.
243      */
244     public static double latFix(final double x) {
245         return Math.abs(x) > 90 ? Double.NaN : x;
246     }
247 
248     /**
249      * The exact difference of two angles reduced to (&minus;180&deg;, 180&deg;].
250      * This computes <i>z</i> = <i>y</i> &minus; <i>x</i> exactly, reduced to (&minus;180&deg;, 180&deg;]; and then sets
251      * <i>z</i> = <i>d</i> + <i>e</i> where <i>d</i> is the nearest representable number to <i>z</i> and <i>e</i> is the
252      * truncation error. If <i>d</i> = &minus;180, then <i>e</i> &gt; 0; If <i>d</i> = 180, then <i>e</i> &le; 0.
253      *
254      * @param x the first angle in degrees.
255      * @param y the second angle in degrees.
256      * @return Pair(<i>d</i>, <i>e</i>) with <i>d</i> being the rounded difference and <i>e</i> being the error.
257      */
258     public static Pair angDiff(final double x, final double y) {
259         //noinspection all
260         final var r = sum(angNormalize(-x), angNormalize(y));
261         final var d = angNormalize(r.getFirst());
262         final var t = r.getSecond();
263 
264         return sum(d == 180 && t > 0 ? -180 : d, t);
265     }
266 
267     /**
268      * Evaluate the sine and cosine function with the argument in degrees.
269      * The results obey exactly the elementary properties of the trigonometric functions, e.g.
270      * sin 9&deg; = cos 81&deg; = &minus; sin 123456789&deg;.
271      *
272      * @param x in degrees.
273      * @return Pair(<i>s</i>, <i>t</i>) with <i>s</i> = sin(<i>x</i> and <i>c</i> = cos(<i>x</i>).
274      */
275     public static Pair sincosd(final double x) {
276         // In order to minimize round-off errors, this function exactly reduces the argument to the range [-45, 45]
277         // before converting it to radians.
278         var r = x % 360.0;
279         final var q = (int) Math.floor(r / 90 + 0.5);
280         r -= 90 * q;
281         // now abs(r) <= 45
282         r = Math.toRadians(r);
283         // Possibly could call the gnu extension sincos
284         final var s = Math.sin(r);
285         final var c = Math.cos(r);
286         double sinx;
287         double cosx;
288         switch (q & 3) {
289             case 0 -> {
290                 sinx = s;
291                 cosx = c;
292             }
293             case 1 -> {
294                 sinx = c;
295                 cosx = -s;
296             }
297             case 2 -> {
298                 sinx = -s;
299                 cosx = -c;
300             }
301             default -> {
302                 //case 3
303                 sinx = -c;
304                 cosx = s;
305             }
306         }
307         if (x != 0) {
308             sinx += 0.0;
309             cosx += 0.0;
310         }
311         return new Pair(sinx, cosx);
312     }
313 
314     /**
315      * Evaluate the atan2 function with the result in degrees.
316      * The result is in the range (&minus;180&deg; 180&deg;]. N.B.,
317      * atan2d(&plusmn;0, &minus;1) = +180&deg;; atan2d(&minus;&epsilon;,&minus;1) = &minus;180&deg;, for &epsilon;
318      * positive and tiny; atan2d(&plusmn;0, 1) = +plusmn;0&deg;.
319      *
320      * @param y the sine of the angle.
321      * @param x the cosine of the angle.
322      * @return atan2(<i>y</i>, <i>x</i>) in degrees.
323      */
324     public static double atan2d(double y, double x) {
325         // In order to minimize round-off errors, this function rearranges the arguments so that result of atan2 is in
326         // the range [-pi/4, pi/4] before converting it to degrees and mapping the result to the correct quadrant.
327         var q = 0;
328         if (Math.abs(y) > Math.abs(x)) {
329             final var t = x;
330             //noinspection all
331             x = y;
332             y = t;
333             q = 2;
334         }
335         if (x < 0) {
336             x = -x;
337             ++q;
338         }
339         // here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4]
340         var ang = Math.toDegrees(Math.atan2(y, x));
341         switch (q) {
342             // Note that atan2d(-0.0, 1.0) will return -0. However, we expect that atan2d will not be called with y =
343             // -0.
344             // If need be, include case 0: ang = 0 + ang; break
345             // and handle mpfr as in angRound.
346             case 1:
347                 ang = (y >= 0 ? 180 : -180) - ang;
348                 break;
349             case 2:
350                 ang = 90 - ang;
351                 break;
352             case 3:
353                 ang = -90 + ang;
354                 break;
355             default:
356                 break;
357         }
358         return ang;
359     }
360 
361     /**
362      * Test for finiteness.
363      *
364      * @param x the argument.
365      * @return true if number is finite, false if NaN or infinite.
366      */
367     public static boolean isFinite(final double x) {
368         return Math.abs(x) <= Double.MAX_VALUE;
369     }
370 }