GeographicLib  2.6
Math.cpp
Go to the documentation of this file.
1 /**
2  * \file Math.cpp
3  * \brief Implementation for GeographicLib::Math class
4  *
5  * Copyright (c) Charles Karney (2015-2024) <karney@alum.mit.edu> and licensed
6  * under the MIT/X11 License. For more information, see
7  * https://geographiclib.sourceforge.io/
8  **********************************************************************/
9 
10 #include <GeographicLib/Math.hpp>
11 
12 namespace GeographicLib {
13 
14  using namespace std;
15 
16  void Math::dummy() {
17  static_assert(GEOGRAPHICLIB_PRECISION >= 1, "Bad value of precision");
18  }
19 
20  int Math::digits() {
21 #if GEOGRAPHICLIB_PRECISION == 5
22  return numeric_limits<real>::digits();
23 #else
24  return numeric_limits<real>::digits;
25 #endif
26  }
27 
28  int Math::set_digits(int ndigits) {
29 #if GEOGRAPHICLIB_PRECISION >= 5
30 # if GEOGRAPHICLIB_PRECISION > 5
31  // This sets ndigits = GEOGRAPHICLIB_PRECISION
32  ndigits = numeric_limits<real>::digits;
33 # endif
34  mpfr::mpreal::set_default_prec(ndigits >= 2 ? ndigits : 2);
35 #else
36  (void) ndigits;
37 #endif
38  return digits();
39  }
40 
42 #if GEOGRAPHICLIB_PRECISION == 5
43  return numeric_limits<real>::digits10();
44 #else
45  return numeric_limits<real>::digits10;
46 #endif
47  }
48 
50  return
51  digits10() > numeric_limits<double>::digits10 ?
52  digits10() - numeric_limits<double>::digits10 : 0;
53  }
54 
55  template<typename T> T Math::sum(T u, T v, T& t) {
56  GEOGRAPHICLIB_VOLATILE T s = u + v;
57  GEOGRAPHICLIB_VOLATILE T up = s - v;
58  GEOGRAPHICLIB_VOLATILE T vpp = s - up;
59  up -= u;
60  vpp -= v;
61  // if s = 0, then t = 0 and give t the same sign as s
62  // mpreal needs T(0) here
63  t = s != 0 ? T(0) - (up + vpp) : s;
64  // u + v = s + t
65  // = round(u + v) + t
66  return s;
67  }
68 
69  template<typename T> T Math::AngNormalize(T x) {
70  T y = remainder(x, T(td));
71 #if GEOGRAPHICLIB_PRECISION == 4
72  // boost-quadmath doesn't set the sign of 0 correctly, see
73  // https://github.com/boostorg/multiprecision/issues/426
74  // Fixed by https://github.com/boostorg/multiprecision/pull/428
75  if (y == 0) y = copysign(y, x);
76 #endif
77  return fabs(y) == T(hd) ? copysign(T(hd), x) : y;
78  }
79 
80  template<typename T> T Math::AngDiff(T x, T y, T& e) {
81  // Use remainder instead of AngNormalize, since we treat boundary cases
82  // later taking account of the error
83  T d = sum(remainder(-x, T(td)), remainder( y, T(td)), e);
84  // This second sum can only change d if abs(d) < 128, so don't need to
85  // apply remainder yet again.
86  d = sum(remainder(d, T(td)), e, e);
87  // Fix the sign if d = -180, 0, 180.
88  if (d == 0 || fabs(d) == hd)
89  // If e == 0, take sign from y - x
90  // else (e != 0, implies d = +/-180), d and e must have opposite signs
91  d = copysign(d, e == 0 ? y - x : -e);
92  return d;
93  }
94 
95  template<typename T> T Math::AngRound(T x) {
96  static const T z = T(1)/T(16);
97  GEOGRAPHICLIB_VOLATILE T y = fabs(x);
98  GEOGRAPHICLIB_VOLATILE T w = z - y;
99  // The compiler mustn't "simplify" z - (z - y) to y
100  y = w > 0 ? z - w : y;
101  return copysign(y, x);
102  }
103 
104  template<typename T> void Math::sincosd(T x, T& sinx, T& cosx) {
105  // In order to minimize round-off errors, this function exactly reduces
106  // the argument to the range [-45, 45] before converting it to radians.
107  int q = 0;
108  T d = remquo(x, T(qd), &q), // now abs(r) <= 45
109  r = d * degree<T>();
110  // g++ -O turns these two function calls into a call to sincos
111  T s = sin(r), c = cos(r);
112  if (2 * fabs(d) == qd) {
113  c = sqrt(1/T(2));
114  s = copysign(c, r);
115  } else if (3 * fabs(d) == qd) {
116  c = sqrt(T(3))/2;
117  s = copysign(1/T(2), r);
118  }
119  switch (unsigned(q) & 3U) {
120  case 0U: sinx = s; cosx = c; break;
121  case 1U: sinx = c; cosx = -s; break;
122  case 2U: sinx = -s; cosx = -c; break;
123  default: sinx = -c; cosx = s; break; // case 3U
124  }
125  // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf
126  // mpreal needs T(0) here
127  cosx += T(0); // special values from F.10.1.12
128  if (sinx == 0) sinx = copysign(sinx, x); // special values from F.10.1.13
129  }
130 
131  template<typename T> void Math::sincosde(T x, T t, T& sinx, T& cosx) {
132  // In order to minimize round-off errors, this function exactly reduces
133  // the argument to the range [-45, 45] before converting it to radians.
134  // This implementation allows x outside [-180, 180], but implementations in
135  // other languages may not.
136  int q = 0;
137  T d = AngRound(remquo(x, T(qd), &q) + t), // now abs(r) <= 45
138  r = d * degree<T>();
139  // g++ -O turns these two function calls into a call to sincos
140  T s = sin(r), c = cos(r);
141  if (2 * fabs(d) == qd) {
142  c = sqrt(1/T(2));
143  s = copysign(c, r);
144  } else if (3 * fabs(d) == qd) {
145  c = sqrt(T(3))/2;
146  s = copysign(1/T(2), r);
147  }
148  switch (unsigned(q) & 3U) {
149  case 0U: sinx = s; cosx = c; break;
150  case 1U: sinx = c; cosx = -s; break;
151  case 2U: sinx = -s; cosx = -c; break;
152  default: sinx = -c; cosx = s; break; // case 3U
153  }
154  // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf
155  // mpreal needs T(0) here
156  cosx += T(0); // special values from F.10.1.12
157  // Should we copy the sign from x or x+t? Given that t is small, there's
158  // only a distinction if x+t == +/- 0. Here are the cases
159  // x t x-sign (x+t)-sign
160  // <0 >0 -0 +0 different
161  // >0 <0 +0 +0
162  // -0 -0 -0 -0
163  // -0 +0 -0 +0 different
164  // +0 -0 +0 +0
165  // +0 +0 +0 +0
166  // On balance, taking the sign from x is better, particularly for the case
167  // x = -0, t = +0. This choice also avoids the bias towards +0 that x+t
168  // gives.
169  if (sinx == 0) sinx = copysign(sinx, x); // special values from F.10.1.13
170  }
171 
172  template<typename T> T Math::sind(T x) {
173  // See sincosd
174  int q = 0;
175  T d = remquo(x, T(qd), &q), // now abs(r) <= 45
176  r = d * degree<T>();
177  unsigned p = unsigned(q);
178  // r = p & 1U ? cos(r) : sin(r); replaced by ...
179  r = p & 1U ? (2 * fabs(d) == qd ? sqrt(1/T(2)) :
180  (3 * fabs(d) == qd ? sqrt(T(3))/2 : cos(r))) :
181  copysign(2 * fabs(d) == qd ? sqrt(1/T(2)) :
182  (3 * fabs(d) == qd ? 1/T(2) : sin(r)), r);
183  if (p & 2U) r = -r;
184  if (r == 0) r = copysign(r, x);
185  return r;
186  }
187 
188  template<typename T> T Math::cosd(T x) {
189  // See sincosd
190  int q = 0;
191  T d = remquo(x, T(qd), &q), // now abs(r) <= 45
192  r = d * degree<T>();
193  unsigned p = unsigned(q + 1);
194  r = p & 1U ? (2 * fabs(d) == qd ? sqrt(1/T(2)) :
195  (3 * fabs(d) == qd ? sqrt(T(3))/2 : cos(r))) :
196  copysign(2 * fabs(d) == qd ? sqrt(1/T(2)) :
197  (3 * fabs(d) == qd ? 1/T(2) : sin(r)), r);
198  if (p & 2U) r = -r;
199  // mpreal needs T(0) here
200  return T(0) + r;
201  }
202 
203  template<typename T> T Math::tand(T x) {
204  static const T overflow = 1 / sq(numeric_limits<T>::epsilon());
205  T s, c;
206  sincosd(x, s, c);
207  // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf
208  T r = s / c; // special values from F.10.1.14
209  return clamp(r, -overflow, overflow);
210  }
211 
212  template<typename T> T Math::atan2d(T y, T x) {
213  // In order to minimize round-off errors, this function rearranges the
214  // arguments so that result of atan2 is in the range [-pi/4, pi/4] before
215  // converting it to degrees and mapping the result to the correct quadrant.
216  // With mpreal we could use T(mpfr::atan2u(y, x, td)); but we're not ready
217  // for this yet.
218  int q = 0;
219  if (fabs(y) > fabs(x)) { swap(x, y); q = 2; }
220  if (signbit(x)) { x = -x; ++q; }
221  // here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4]
222  // Replace atan2(y, x) / degree<T>() by this to ensure that special values
223  // (45, 90, etc.) are returned.
224  T ang = (atan2(y, x) / pi<T>()) * T(hd);
225  switch (q) {
226  case 1: ang = copysign(T(hd), y) - ang; break;
227  case 2: ang = qd - ang; break;
228  case 3: ang = -qd + ang; break;
229  default: break;
230  }
231  return ang;
232  }
233 
234  template<typename T> T Math::atand(T x)
235  { return atan2d(x, T(1)); }
236 
237  template<typename T> T Math::eatanhe(T x, T es) {
238  return es > 0 ? es * atanh(es * x) : -es * atan(es * x);
239  }
240 
241  template<typename T> T Math::taupf(T tau, T es) {
242  // Need this test, otherwise tau = +/-inf gives taup = nan.
243  if (isfinite(tau)) {
244  T tau1 = hypot(T(1), tau),
245  sig = sinh( eatanhe(tau / tau1, es ) );
246  return hypot(T(1), sig) * tau - sig * tau1;
247  } else
248  return tau;
249  }
250 
251  template<typename T> T Math::tauf(T taup, T es) {
252  static const int numit = 5;
253  // min iterations = 1, max iterations = 2; mean = 1.95
254  static const T tol = sqrt(numeric_limits<T>::epsilon()) / 10;
255  static const T taumax = 2 / sqrt(numeric_limits<T>::epsilon());
256  T e2m = 1 - sq(es),
257  // To lowest order in e^2, taup = (1 - e^2) * tau = _e2m * tau; so use
258  // tau = taup/e2m as a starting guess. Only 1 iteration is needed for
259  // |lat| < 3.35 deg, otherwise 2 iterations are needed. If, instead, tau
260  // = taup is used the mean number of iterations increases to 1.999 (2
261  // iterations are needed except near tau = 0).
262  //
263  // For large tau, taup = exp(-es*atanh(es)) * tau. Use this as for the
264  // initial guess for |taup| > 70 (approx |phi| > 89deg). Then for
265  // sufficiently large tau (such that sqrt(1+tau^2) = |tau|), we can exit
266  // with the intial guess and avoid overflow problems. This also reduces
267  // the mean number of iterations slightly from 1.963 to 1.954.
268  tau = fabs(taup) > 70 ? taup * exp(eatanhe(T(1), es)) : taup/e2m,
269  stol = tol * fmax(T(1), fabs(taup));
270  if (!(fabs(tau) < taumax)) return tau; // handles +/-inf and nan
271  for (int i = 0;
272  i < numit ||
273  GEOGRAPHICLIB_PANIC("Convergence failure in Math::tauf");
274  ++i) {
275  T taupa = taupf(tau, es),
276  dtau = (taup - taupa) * (1 + e2m * sq(tau)) /
277  ( e2m * hypot(T(1), tau) * hypot(T(1), taupa) );
278  tau += dtau;
279  if (!(fabs(dtau) >= stol))
280  break;
281  }
282  return tau;
283  }
284 
285  template<typename T> T Math::hypot3(T x, T y, T z) {
286 #if GEOGRAPHICLIB_PRECISION == 4
287  // Boost implementation is given by
288  // https://github.com/boostorg/math/pull/1318
289  // might make its way into 1.90 or later
290  return hypot(hypot(x, y), z);
291 #else
292  return hypot(x, y, z);
293 #endif
294  }
295 
296  template<typename T> T Math::clamp(T x, T a, T b) {
297  // Use max/min here (instead of fmax/fmin) to preserve NaN
298  return min(max(x, a), b);
299  }
300 
301  template<typename T> T Math::NaN() {
302  if constexpr (numeric_limits<T>::has_quiet_NaN)
303  return numeric_limits<T>::quiet_NaN();
304  else
305  return (numeric_limits<T>::max)();
306  }
307 
308  template<typename T> T Math::infinity() {
309  if constexpr (numeric_limits<T>::has_infinity)
310  return numeric_limits<T>::infinity();
311  else
312  return (numeric_limits<T>::max)();
313  }
314 
315  /// \cond SKIP
316  // Instantiate
317 #define GEOGRAPHICLIB_MATH_INSTANTIATE(T) \
318  template T GEOGRAPHICLIB_EXPORT Math::sum <T>(T, T, T&); \
319  template T GEOGRAPHICLIB_EXPORT Math::AngNormalize <T>(T); \
320  template T GEOGRAPHICLIB_EXPORT Math::AngDiff <T>(T, T, T&); \
321  template T GEOGRAPHICLIB_EXPORT Math::AngRound <T>(T); \
322  template void GEOGRAPHICLIB_EXPORT Math::sincosd <T>(T, T&, T&); \
323  template void GEOGRAPHICLIB_EXPORT Math::sincosde <T>(T, T, T&, T&); \
324  template T GEOGRAPHICLIB_EXPORT Math::sind <T>(T); \
325  template T GEOGRAPHICLIB_EXPORT Math::cosd <T>(T); \
326  template T GEOGRAPHICLIB_EXPORT Math::tand <T>(T); \
327  template T GEOGRAPHICLIB_EXPORT Math::atan2d <T>(T, T); \
328  template T GEOGRAPHICLIB_EXPORT Math::atand <T>(T); \
329  template T GEOGRAPHICLIB_EXPORT Math::eatanhe <T>(T, T); \
330  template T GEOGRAPHICLIB_EXPORT Math::taupf <T>(T, T); \
331  template T GEOGRAPHICLIB_EXPORT Math::tauf <T>(T, T); \
332  template T GEOGRAPHICLIB_EXPORT Math::hypot3 <T>(T, T, T); \
333  template T GEOGRAPHICLIB_EXPORT Math::clamp <T>(T, T, T); \
334  template T GEOGRAPHICLIB_EXPORT Math::NaN <T>(); \
335  template T GEOGRAPHICLIB_EXPORT Math::infinity <T>();
336 
337  // Instantiate with the standard floating type
338  GEOGRAPHICLIB_MATH_INSTANTIATE(float)
339  GEOGRAPHICLIB_MATH_INSTANTIATE(double)
340 #if GEOGRAPHICLIB_HAVE_LONG_DOUBLE
341  // Instantiate if long double is distinct from double
342  GEOGRAPHICLIB_MATH_INSTANTIATE(long double)
343 #endif
344 #if GEOGRAPHICLIB_PRECISION > 3
345  // Instantiate with the high precision type
346  GEOGRAPHICLIB_MATH_INSTANTIATE(Math::real)
347 #endif
348 
349 #undef GEOGRAPHICLIB_MATH_INSTANTIATE
350 
351  // Also we need int versions for Utility::nummatch
352 #define GEOGRAPHICLIB_MATH_INSTANTIATE2(T) \
353  template T GEOGRAPHICLIB_EXPORT Math::NaN <T>(); \
354  template T GEOGRAPHICLIB_EXPORT Math::infinity<T>();
355 
356  GEOGRAPHICLIB_MATH_INSTANTIATE2(int)
357  GEOGRAPHICLIB_MATH_INSTANTIATE2(unsigned long long)
358 
359 #undef GEOGRAPHICLIB_MATH_INSTANTIATE2
360  /// \endcond
361 
362 } // namespace GeographicLib
static T atand(T x)
Definition: Math.cpp:234
static T tand(T x)
Definition: Math.cpp:203
static T infinity()
Definition: Math.cpp:308
static T clamp(T x, T a, T b)
Definition: Math.cpp:296
static void sincosde(T x, T t, T &sinx, T &cosx)
Definition: Math.cpp:131
#define GEOGRAPHICLIB_PRECISION
Definition: Math.hpp:35
#define GEOGRAPHICLIB_VOLATILE
Definition: Math.hpp:64
static int extra_digits()
Definition: Math.cpp:49
static T atan2d(T y, T x)
Definition: Math.cpp:212
Header for GeographicLib::Math class.
static int set_digits(int ndigits)
Definition: Math.cpp:28
static T AngNormalize(T x)
Definition: Math.cpp:69
Namespace for GeographicLib.
Definition: Accumulator.cpp:12
static T NaN()
Definition: Math.cpp:301
static T sum(T u, T v, T &t)
Definition: Math.cpp:55
static T hypot3(T x, T y, T z)
Definition: Math.cpp:285
static T AngDiff(T x, T y, T &e)
Definition: Math.cpp:80
void swap(GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &a, GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &b)
static T sind(T x)
Definition: Math.cpp:172
static T cosd(T x)
Definition: Math.cpp:188
#define GEOGRAPHICLIB_PANIC(msg)
Definition: Math.hpp:67
static int digits10()
Definition: Math.cpp:41
static T tauf(T taup, T es)
Definition: Math.cpp:251
static void sincosd(T x, T &sinx, T &cosx)
Definition: Math.cpp:104
static T eatanhe(T x, T es)
Definition: Math.cpp:237
static int digits()
Definition: Math.cpp:20
GeographicLib::Angle ang
Definition: Geod3Solve.cpp:26
static T taupf(T tau, T es)
Definition: Math.cpp:241
static T AngRound(T x)
Definition: Math.cpp:95