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 }