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> = ∑<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>−<i>n</i></sup>.
182 * Return 0 if <i>n</i> < 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 [−180°, 180°).
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 [−90°, 90°], 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 (−180°, 180°].
250 * This computes <i>z</i> = <i>y</i> − <i>x</i> exactly, reduced to (−180°, 180°]; 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> = −180, then <i>e</i> > 0; If <i>d</i> = 180, then <i>e</i> ≤ 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° = cos 81° = − sin 123456789°.
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 (−180° 180°]. N.B.,
317 * atan2d(±0, −1) = +180°; atan2d(−ε,−1) = −180°, for ε
318 * positive and tiny; atan2d(±0, 1) = +plusmn;0°.
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 }