/** * \file Math.cpp * \brief Implementation for GeographicLib::Math class * * Copyright (c) Charles Karney (2015-2022) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #include #if defined(_MSC_VER) // Squelch warnings about constant conditional and enum-float expressions # pragma warning (disable: 4127 5055) #endif namespace GeographicLib { using namespace std; void Math::dummy() { static_assert(GEOGRAPHICLIB_PRECISION >= 1 && GEOGRAPHICLIB_PRECISION <= 5, "Bad value of precision"); } int Math::digits() { #if GEOGRAPHICLIB_PRECISION != 5 return numeric_limits::digits; #else return numeric_limits::digits(); #endif } int Math::set_digits(int ndigits) { #if GEOGRAPHICLIB_PRECISION != 5 (void)ndigits; #else mpfr::mpreal::set_default_prec(ndigits >= 2 ? ndigits : 2); #endif return digits(); } int Math::digits10() { #if GEOGRAPHICLIB_PRECISION != 5 return numeric_limits::digits10; #else return numeric_limits::digits10(); #endif } int Math::extra_digits() { return digits10() > numeric_limits::digits10 ? digits10() - numeric_limits::digits10 : 0; } template T Math::sum(T u, T v, T& t) { GEOGRAPHICLIB_VOLATILE T s = u + v; GEOGRAPHICLIB_VOLATILE T up = s - v; GEOGRAPHICLIB_VOLATILE T vpp = s - up; up -= u; vpp -= v; // if s = 0, then t = 0 and give t the same sign as s // mpreal needs T(0) here t = s != 0 ? T(0) - (up + vpp) : s; // u + v = s + t // = round(u + v) + t return s; } template T Math::AngNormalize(T x) { T y = remainder(x, T(td)); #if GEOGRAPHICLIB_PRECISION == 4 // boost-quadmath doesn't set the sign of 0 correctly, see // https://github.com/boostorg/multiprecision/issues/426 // Fixed by https://github.com/boostorg/multiprecision/pull/428 if (y == 0) y = copysign(y, x); #endif return fabs(y) == T(hd) ? copysign(T(hd), x) : y; } template T Math::AngDiff(T x, T y, T& e) { // Use remainder instead of AngNormalize, since we treat boundary cases // later taking account of the error T d = sum(remainder(-x, T(td)), remainder( y, T(td)), e); // This second sum can only change d if abs(d) < 128, so don't need to // apply remainder yet again. d = sum(remainder(d, T(td)), e, e); // Fix the sign if d = -180, 0, 180. if (d == 0 || fabs(d) == hd) // If e == 0, take sign from y - x // else (e != 0, implies d = +/-180), d and e must have opposite signs d = copysign(d, e == 0 ? y - x : -e); return d; } template T Math::AngRound(T x) { static const T z = T(1)/T(16); GEOGRAPHICLIB_VOLATILE T y = fabs(x); GEOGRAPHICLIB_VOLATILE T w = z - y; // The compiler mustn't "simplify" z - (z - y) to y y = w > 0 ? z - w : y; return copysign(y, x); } template void Math::sincosd(T x, T& sinx, T& cosx) { // In order to minimize round-off errors, this function exactly reduces // the argument to the range [-45, 45] before converting it to radians. T r; int q = 0; r = remquo(x, T(qd), &q); // now abs(r) <= 45 r *= degree(); // g++ -O turns these two function calls into a call to sincos T s = sin(r), c = cos(r); switch (unsigned(q) & 3U) { case 0U: sinx = s; cosx = c; break; case 1U: sinx = c; cosx = -s; break; case 2U: sinx = -s; cosx = -c; break; default: sinx = -c; cosx = s; break; // case 3U } // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf // mpreal needs T(0) here cosx += T(0); // special values from F.10.1.12 if (sinx == 0) sinx = copysign(sinx, x); // special values from F.10.1.13 } template void Math::sincosde(T x, T t, T& sinx, T& cosx) { // In order to minimize round-off errors, this function exactly reduces // the argument to the range [-45, 45] before converting it to radians. // This implementation allows x outside [-180, 180], but implementations in // other languages may not. T r; int q = 0; r = AngRound(remquo(x, T(qd), &q) + t); // now abs(r) <= 45 r *= degree(); // g++ -O turns these two function calls into a call to sincos T s = sin(r), c = cos(r); switch (unsigned(q) & 3U) { case 0U: sinx = s; cosx = c; break; case 1U: sinx = c; cosx = -s; break; case 2U: sinx = -s; cosx = -c; break; default: sinx = -c; cosx = s; break; // case 3U } // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf // mpreal needs T(0) here cosx += T(0); // special values from F.10.1.12 if (sinx == 0) sinx = copysign(sinx, x); // special values from F.10.1.13 } template T Math::sind(T x) { // See sincosd T r; int q = 0; r = remquo(x, T(qd), &q); // now abs(r) <= 45 r *= degree(); unsigned p = unsigned(q); r = p & 1U ? cos(r) : sin(r); if (p & 2U) r = -r; if (r == 0) r = copysign(r, x); return r; } template T Math::cosd(T x) { // See sincosd T r; int q = 0; r = remquo(x, T(qd), &q); // now abs(r) <= 45 r *= degree(); unsigned p = unsigned(q + 1); r = p & 1U ? cos(r) : sin(r); if (p & 2U) r = -r; // mpreal needs T(0) here return T(0) + r; } template T Math::tand(T x) { static const T overflow = 1 / sq(numeric_limits::epsilon()); T s, c; sincosd(x, s, c); // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf T r = s / c; // special values from F.10.1.14 // With C++17 this becomes clamp(s / c, -overflow, overflow); // Use max/min here (instead of fmax/fmin) to preserve NaN return min(max(r, -overflow), overflow); } template T Math::atan2d(T y, T x) { // In order to minimize round-off errors, this function rearranges the // arguments so that result of atan2 is in the range [-pi/4, pi/4] before // converting it to degrees and mapping the result to the correct // quadrant. int q = 0; if (fabs(y) > fabs(x)) { swap(x, y); q = 2; } if (signbit(x)) { x = -x; ++q; } // here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4] T ang = atan2(y, x) / degree(); switch (q) { case 1: ang = copysign(T(hd), y) - ang; break; case 2: ang = qd - ang; break; case 3: ang = -qd + ang; break; default: break; } return ang; } template T Math::atand(T x) { return atan2d(x, T(1)); } template T Math::eatanhe(T x, T es) { return es > 0 ? es * atanh(es * x) : -es * atan(es * x); } template T Math::taupf(T tau, T es) { // Need this test, otherwise tau = +/-inf gives taup = nan. if (isfinite(tau)) { T tau1 = hypot(T(1), tau), sig = sinh( eatanhe(tau / tau1, es ) ); return hypot(T(1), sig) * tau - sig * tau1; } else return tau; } template T Math::tauf(T taup, T es) { static const int numit = 5; // min iterations = 1, max iterations = 2; mean = 1.95 static const T tol = sqrt(numeric_limits::epsilon()) / 10; static const T taumax = 2 / sqrt(numeric_limits::epsilon()); T e2m = 1 - sq(es), // To lowest order in e^2, taup = (1 - e^2) * tau = _e2m * tau; so use // tau = taup/e2m as a starting guess. Only 1 iteration is needed for // |lat| < 3.35 deg, otherwise 2 iterations are needed. If, instead, tau // = taup is used the mean number of iterations increases to 1.999 (2 // iterations are needed except near tau = 0). // // For large tau, taup = exp(-es*atanh(es)) * tau. Use this as for the // initial guess for |taup| > 70 (approx |phi| > 89deg). Then for // sufficiently large tau (such that sqrt(1+tau^2) = |tau|), we can exit // with the intial guess and avoid overflow problems. This also reduces // the mean number of iterations slightly from 1.963 to 1.954. tau = fabs(taup) > 70 ? taup * exp(eatanhe(T(1), es)) : taup/e2m, stol = tol * fmax(T(1), fabs(taup)); if (!(fabs(tau) < taumax)) return tau; // handles +/-inf and nan for (int i = 0; i < numit || GEOGRAPHICLIB_PANIC; ++i) { T taupa = taupf(tau, es), dtau = (taup - taupa) * (1 + e2m * sq(tau)) / ( e2m * hypot(T(1), tau) * hypot(T(1), taupa) ); tau += dtau; if (!(fabs(dtau) >= stol)) break; } return tau; } template T Math::NaN() { #if defined(_MSC_VER) return numeric_limits::has_quiet_NaN ? numeric_limits::quiet_NaN() : (numeric_limits::max)(); #else return numeric_limits::has_quiet_NaN ? numeric_limits::quiet_NaN() : numeric_limits::max(); #endif } template T Math::infinity() { #if defined(_MSC_VER) return numeric_limits::has_infinity ? numeric_limits::infinity() : (numeric_limits::max)(); #else return numeric_limits::has_infinity ? numeric_limits::infinity() : numeric_limits::max(); #endif } /// \cond SKIP // Instantiate #define GEOGRAPHICLIB_MATH_INSTANTIATE(T) \ template T GEOGRAPHICLIB_EXPORT Math::sum (T, T, T&); \ template T GEOGRAPHICLIB_EXPORT Math::AngNormalize (T); \ template T GEOGRAPHICLIB_EXPORT Math::AngDiff (T, T, T&); \ template T GEOGRAPHICLIB_EXPORT Math::AngRound (T); \ template void GEOGRAPHICLIB_EXPORT Math::sincosd (T, T&, T&); \ template void GEOGRAPHICLIB_EXPORT Math::sincosde (T, T, T&, T&); \ template T GEOGRAPHICLIB_EXPORT Math::sind (T); \ template T GEOGRAPHICLIB_EXPORT Math::cosd (T); \ template T GEOGRAPHICLIB_EXPORT Math::tand (T); \ template T GEOGRAPHICLIB_EXPORT Math::atan2d (T, T); \ template T GEOGRAPHICLIB_EXPORT Math::atand (T); \ template T GEOGRAPHICLIB_EXPORT Math::eatanhe (T, T); \ template T GEOGRAPHICLIB_EXPORT Math::taupf (T, T); \ template T GEOGRAPHICLIB_EXPORT Math::tauf (T, T); \ template T GEOGRAPHICLIB_EXPORT Math::NaN (); \ template T GEOGRAPHICLIB_EXPORT Math::infinity (); // Instantiate with the standard floating type GEOGRAPHICLIB_MATH_INSTANTIATE(float) GEOGRAPHICLIB_MATH_INSTANTIATE(double) #if GEOGRAPHICLIB_HAVE_LONG_DOUBLE // Instantiate if long double is distinct from double GEOGRAPHICLIB_MATH_INSTANTIATE(long double) #endif #if GEOGRAPHICLIB_PRECISION > 3 // Instantiate with the high precision type GEOGRAPHICLIB_MATH_INSTANTIATE(Math::real) #endif #undef GEOGRAPHICLIB_MATH_INSTANTIATE // Also we need int versions for Utility::nummatch template int GEOGRAPHICLIB_EXPORT Math::NaN (); template int GEOGRAPHICLIB_EXPORT Math::infinity(); /// \endcond } // namespace GeographicLib