/** * \file Math.hpp * \brief Header for GeographicLib::Math class * * Copyright (c) Charles Karney (2008-2022) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ // Constants.hpp includes Math.hpp. Place this include outside Math.hpp's // include guard to enforce this ordering. #include #if !defined(GEOGRAPHICLIB_MATH_HPP) #define GEOGRAPHICLIB_MATH_HPP 1 #if !defined(GEOGRAPHICLIB_WORDS_BIGENDIAN) # define GEOGRAPHICLIB_WORDS_BIGENDIAN 0 #endif #if !defined(GEOGRAPHICLIB_HAVE_LONG_DOUBLE) # define GEOGRAPHICLIB_HAVE_LONG_DOUBLE 0 #endif #if !defined(GEOGRAPHICLIB_PRECISION) /** * The precision of floating point numbers used in %GeographicLib. 1 means * float (single precision); 2 (the default) means double; 3 means long double; * 4 is reserved for quadruple precision. Nearly all the testing has been * carried out with doubles and that's the recommended configuration. In order * for long double to be used, GEOGRAPHICLIB_HAVE_LONG_DOUBLE needs to be * defined. Note that with Microsoft Visual Studio, long double is the same as * double. **********************************************************************/ # define GEOGRAPHICLIB_PRECISION 2 #endif #include #include #include #if GEOGRAPHICLIB_PRECISION == 4 #include #include #include #elif GEOGRAPHICLIB_PRECISION == 5 #include #endif #if GEOGRAPHICLIB_PRECISION > 3 // volatile keyword makes no sense for multiprec types #define GEOGRAPHICLIB_VOLATILE // Signal a convergence failure with multiprec types by throwing an exception // at loop exit. #define GEOGRAPHICLIB_PANIC \ (throw GeographicLib::GeographicErr("Convergence failure"), false) #else #define GEOGRAPHICLIB_VOLATILE volatile // Ignore convergence failures with standard floating points types by allowing // loop to exit cleanly. #define GEOGRAPHICLIB_PANIC false #endif namespace GeographicLib { /** * \brief Mathematical functions needed by %GeographicLib * * Define mathematical functions in order to localize system dependencies and * to provide generic versions of the functions. In addition define a real * type to be used by %GeographicLib. * * Example of use: * \include example-Math.cpp **********************************************************************/ class GEOGRAPHICLIB_EXPORT Math { private: void dummy(); // Static check for GEOGRAPHICLIB_PRECISION Math() = delete; // Disable constructor public: #if GEOGRAPHICLIB_HAVE_LONG_DOUBLE /** * The extended precision type for real numbers, used for some testing. * This is long double on computers with this type; otherwise it is double. **********************************************************************/ typedef long double extended; #else typedef double extended; #endif #if GEOGRAPHICLIB_PRECISION == 2 /** * The real type for %GeographicLib. Nearly all the testing has been done * with \e real = double. However, the algorithms should also work with * float and long double (where available). (CAUTION: reasonable * accuracy typically cannot be obtained using floats.) **********************************************************************/ typedef double real; #elif GEOGRAPHICLIB_PRECISION == 1 typedef float real; #elif GEOGRAPHICLIB_PRECISION == 3 typedef extended real; #elif GEOGRAPHICLIB_PRECISION == 4 typedef boost::multiprecision::float128 real; #elif GEOGRAPHICLIB_PRECISION == 5 typedef mpfr::mpreal real; #else typedef double real; #endif /** * The constants defining the standard (Babylonian) meanings of degrees, * minutes, and seconds, for angles. Read the constants as follows (for * example): \e ms = 60 is the ratio 1 minute / 1 second. The * abbreviations are * - \e t a whole turn (360°) * - \e h a half turn (180°) * - \e q a quarter turn (a right angle = 90°) * - \e d a degree * - \e m a minute * - \e s a second * . * Note that degree() is ratio 1 degree / 1 radian, thus, for example, * Math::degree() * Math::qd is the ratio 1 quarter turn / 1 radian = * π/2. * * Defining all these in one place would mean that it's simple to convert * to the centesimal system for measuring angles. The DMS class assumes * that Math::dm and Math::ms are less than or equal to 100 (so that two * digits suffice for the integer parts of the minutes and degrees * components of an angle). Switching to the centesimal convention will * break most of the tests. Also the normal definition of degree is baked * into some classes, e.g., UTMUPS, MGRS, Georef, Geohash, etc. **********************************************************************/ #if GEOGRAPHICLIB_PRECISION == 4 static const int #else enum dms { #endif qd = 90, ///< degrees per quarter turn dm = 60, ///< minutes per degree ms = 60, ///< seconds per minute hd = 2 * qd, ///< degrees per half turn td = 2 * hd, ///< degrees per turn ds = dm * ms ///< seconds per degree #if GEOGRAPHICLIB_PRECISION == 4 ; #else }; #endif /** * @return the number of bits of precision in a real number. **********************************************************************/ static int digits(); /** * Set the binary precision of a real number. * * @param[in] ndigits the number of bits of precision. * @return the resulting number of bits of precision. * * This only has an effect when GEOGRAPHICLIB_PRECISION = 5. See also * Utility::set_digits for caveats about when this routine should be * called. **********************************************************************/ static int set_digits(int ndigits); /** * @return the number of decimal digits of precision in a real number. **********************************************************************/ static int digits10(); /** * Number of additional decimal digits of precision for real relative to * double (0 for float). **********************************************************************/ static int extra_digits(); /** * true if the machine is big-endian. **********************************************************************/ static const bool bigendian = GEOGRAPHICLIB_WORDS_BIGENDIAN; /** * @tparam T the type of the returned value. * @return π. **********************************************************************/ template static T pi() { using std::atan2; static const T pi = atan2(T(0), T(-1)); return pi; } /** * @tparam T the type of the returned value. * @return the number of radians in a degree. **********************************************************************/ template static T degree() { static const T degree = pi() / T(hd); return degree; } /** * Square a number. * * @tparam T the type of the argument and the returned value. * @param[in] x * @return x2. **********************************************************************/ template static T sq(T x) { return x * x; } /** * Normalize a two-vector. * * @tparam T the type of the argument and the returned value. * @param[in,out] x on output set to x/hypot(x, y). * @param[in,out] y on output set to y/hypot(x, y). **********************************************************************/ template static void norm(T& x, T& y) { #if defined(_MSC_VER) && defined(_M_IX86) // hypot for Visual Studio (A=win32) fails monotonicity, e.g., with // x = 0.6102683302836215 // y1 = 0.7906090004346522 // y2 = y1 + 1e-16 // the test // hypot(x, y2) >= hypot(x, y1) // fails. Reported 2021-03-14: // https://developercommunity.visualstudio.com/t/1369259 // See also: // https://bugs.python.org/issue43088 using std::sqrt; T h = sqrt(x * x + y * y); #else using std::hypot; T h = hypot(x, y); #endif x /= h; y /= h; } /** * The error-free sum of two numbers. * * @tparam T the type of the argument and the returned value. * @param[in] u * @param[in] v * @param[out] t the exact error given by (\e u + \e v) - \e s. * @return \e s = round(\e u + \e v). * * See D. E. Knuth, TAOCP, Vol 2, 4.2.2, Theorem B. * * \note \e t can be the same as one of the first two arguments. **********************************************************************/ template static T sum(T u, T v, T& t); /** * Evaluate a polynomial. * * @tparam T the type of the arguments and returned value. * @param[in] N the order of the polynomial. * @param[in] p the coefficient array (of size \e N + 1) with * p0 being coefficient of xN. * @param[in] x the variable. * @return the value of the polynomial. * * Evaluate ∑n=0..N * pn xNn. * Return 0 if \e N < 0. Return p0, if \e N = 0 (even * if \e x is infinite or a nan). The evaluation uses Horner's method. **********************************************************************/ template static T polyval(int N, const T p[], T x) { // This used to employ Math::fma; but that's too slow and it seemed not // to improve the accuracy noticeably. This might change when there's // direct hardware support for fma. T y = N < 0 ? 0 : *p++; while (--N >= 0) y = y * x + *p++; return y; } /** * Normalize an angle. * * @tparam T the type of the argument and returned value. * @param[in] x the angle in degrees. * @return the angle reduced to the range [−180°, 180°]. * * The range of \e x is unrestricted. If the result is ±0° or * ±180° then the sign is the sign of \e x. **********************************************************************/ template static T AngNormalize(T x); /** * Normalize a latitude. * * @tparam T the type of the argument and returned value. * @param[in] x the angle in degrees. * @return x if it is in the range [−90°, 90°], otherwise * return NaN. **********************************************************************/ template static T LatFix(T x) { using std::fabs; return fabs(x) > T(qd) ? NaN() : x; } /** * The exact difference of two angles reduced to * [−180°, 180°]. * * @tparam T the type of the arguments and returned value. * @param[in] x the first angle in degrees. * @param[in] y the second angle in degrees. * @param[out] e the error term in degrees. * @return \e d, the truncated value of \e y − \e x. * * This computes \e z = \e y − \e x exactly, reduced to * [−180°, 180°]; and then sets \e z = \e d + \e e where \e d * is the nearest representable number to \e z and \e e is the truncation * error. If \e z = ±0° or ±180°, then the sign of * \e d is given by the sign of \e y − \e x. The maximum absolute * value of \e e is 2−26 (for doubles). **********************************************************************/ template static T AngDiff(T x, T y, T& e); /** * Difference of two angles reduced to [−180°, 180°] * * @tparam T the type of the arguments and returned value. * @param[in] x the first angle in degrees. * @param[in] y the second angle in degrees. * @return \e y − \e x, reduced to the range [−180°, * 180°]. * * The result is equivalent to computing the difference exactly, reducing * it to [−180°, 180°] and rounding the result. **********************************************************************/ template static T AngDiff(T x, T y) { T e; return AngDiff(x, y, e); } /** * Coarsen a value close to zero. * * @tparam T the type of the argument and returned value. * @param[in] x * @return the coarsened value. * * The makes the smallest gap in \e x = 1/16 − nextafter(1/16, 0) = * 1/257 for doubles = 0.8 pm on the earth if \e x is an angle * in degrees. (This is about 2000 times more resolution than we get with * angles around 90°.) We use this to avoid having to deal with near * singular cases when \e x is non-zero but tiny (e.g., * 10−200). This sign of ±0 is preserved. **********************************************************************/ template static T AngRound(T x); /** * Evaluate the sine and cosine function with the argument in degrees * * @tparam T the type of the arguments. * @param[in] x in degrees. * @param[out] sinx sin(x). * @param[out] cosx cos(x). * * The results obey exactly the elementary properties of the trigonometric * functions, e.g., sin 9° = cos 81° = − sin 123456789°. * If x = −0 or a negative multiple of 180°, then \e sinx = * −0; this is the only case where −0 is returned. **********************************************************************/ template static void sincosd(T x, T& sinx, T& cosx); /** * Evaluate the sine and cosine with reduced argument plus correction * * @tparam T the type of the arguments. * @param[in] x reduced angle in degrees. * @param[in] t correction in degrees. * @param[out] sinx sin(x + t). * @param[out] cosx cos(x + t). * * This is a variant of Math::sincosd allowing a correction to the angle to * be supplied. \e x must be in [−180°, 180°] and \e t is * assumed to be a small correction. Math::AngRound is applied to * the reduced angle to prevent problems with \e x + \e t being extremely * close but not exactly equal to one of the four cardinal directions. **********************************************************************/ template static void sincosde(T x, T t, T& sinx, T& cosx); /** * Evaluate the sine function with the argument in degrees * * @tparam T the type of the argument and the returned value. * @param[in] x in degrees. * @return sin(x). * * The result is +0 for \e x = +0 and positive multiples of 180°. The * result is −0 for \e x = -0 and negative multiples of 180°. **********************************************************************/ template static T sind(T x); /** * Evaluate the cosine function with the argument in degrees * * @tparam T the type of the argument and the returned value. * @param[in] x in degrees. * @return cos(x). * * The result is +0 for \e x an odd multiple of 90°. **********************************************************************/ template static T cosd(T x); /** * Evaluate the tangent function with the argument in degrees * * @tparam T the type of the argument and the returned value. * @param[in] x in degrees. * @return tan(x). * * If \e x is an odd multiple of 90°, then a suitably large (but * finite) value is returned. **********************************************************************/ template static T tand(T x); /** * Evaluate the atan2 function with the result in degrees * * @tparam T the type of the arguments and the returned value. * @param[in] y * @param[in] x * @return atan2(y, x) in degrees. * * The result is in the range [−180° 180°]. N.B., * atan2d(±0, −1) = ±180°. **********************************************************************/ template static T atan2d(T y, T x); /** * Evaluate the atan function with the result in degrees * * @tparam T the type of the argument and the returned value. * @param[in] x * @return atan(x) in degrees. **********************************************************************/ template static T atand(T x); /** * Evaluate e atanh(e x) * * @tparam T the type of the argument and the returned value. * @param[in] x * @param[in] es the signed eccentricity = sign(e2) * sqrt(|e2|) * @return e atanh(e x) * * If e2 is negative (e is imaginary), the * expression is evaluated in terms of atan. **********************************************************************/ template static T eatanhe(T x, T es); /** * tanχ in terms of tanφ * * @tparam T the type of the argument and the returned value. * @param[in] tau τ = tanφ * @param[in] es the signed eccentricity = sign(e2) * sqrt(|e2|) * @return τ′ = tanχ * * See Eqs. (7--9) of * C. F. F. Karney, * * Transverse Mercator with an accuracy of a few nanometers, * J. Geodesy 85(8), 475--485 (Aug. 2011) * (preprint * arXiv:1002.1417). **********************************************************************/ template static T taupf(T tau, T es); /** * tanφ in terms of tanχ * * @tparam T the type of the argument and the returned value. * @param[in] taup τ′ = tanχ * @param[in] es the signed eccentricity = sign(e2) * sqrt(|e2|) * @return τ = tanφ * * See Eqs. (19--21) of * C. F. F. Karney, * * Transverse Mercator with an accuracy of a few nanometers, * J. Geodesy 85(8), 475--485 (Aug. 2011) * (preprint * arXiv:1002.1417). **********************************************************************/ template static T tauf(T taup, T es); /** * The NaN (not a number) * * @tparam T the type of the returned value. * @return NaN if available, otherwise return the max real of type T. **********************************************************************/ template static T NaN(); /** * Infinity * * @tparam T the type of the returned value. * @return infinity if available, otherwise return the max real. **********************************************************************/ template static T infinity(); /** * Swap the bytes of a quantity * * @tparam T the type of the argument and the returned value. * @param[in] x * @return x with its bytes swapped. **********************************************************************/ template static T swab(T x) { union { T r; unsigned char c[sizeof(T)]; } b; b.r = x; for (int i = sizeof(T)/2; i--; ) std::swap(b.c[i], b.c[sizeof(T) - 1 - i]); return b.r; } }; } // namespace GeographicLib #endif // GEOGRAPHICLIB_MATH_HPP