GeographicLib: Math.cpp Source File

GeographicLib 2.5.2

Loading...
Searching...
No Matches
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
12namespace 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
41 int Math::digits10() {
42#if GEOGRAPHICLIB_PRECISION == 5
43 return numeric_limits<real>::digits10();
44#else
45 return numeric_limits<real>::digits10;
46#endif
47 }
48
49 int Math::extra_digits() {
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 T d, r; int q = 0;
108 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 if (sinx == 0) sinx = copysign(sinx, x+t); // special values from F.10.1.13
158 }
159
160 template<typename T> T Math::sind(T x) {
161 // See sincosd
162 int q = 0;
163 T d = remquo(x, T(qd), &q), // now abs(r) <= 45
164 r = d * degree<T>();
165 unsigned p = unsigned(q);
166 // r = p & 1U ? cos(r) : sin(r); replaced by ...
167 r = p & 1U ? (2 * fabs(d) == qd ? sqrt(1/T(2)) :
168 (3 * fabs(d) == qd ? sqrt(T(3))/2 : cos(r))) :
169 copysign(2 * fabs(d) == qd ? sqrt(1/T(2)) :
170 (3 * fabs(d) == qd ? 1/T(2) : sin(r)), r);
171 if (p & 2U) r = -r;
172 if (r == 0) r = copysign(r, x);
173 return r;
174 }
175
176 template<typename T> T Math::cosd(T x) {
177 // See sincosd
178 int q = 0;
179 T d = remquo(x, T(qd), &q), // now abs(r) <= 45
180 r = d * degree<T>();
181 unsigned p = unsigned(q + 1);
182 r = p & 1U ? (2 * fabs(d) == qd ? sqrt(1/T(2)) :
183 (3 * fabs(d) == qd ? sqrt(T(3))/2 : cos(r))) :
184 copysign(2 * fabs(d) == qd ? sqrt(1/T(2)) :
185 (3 * fabs(d) == qd ? 1/T(2) : sin(r)), r);
186 if (p & 2U) r = -r;
187 // mpreal needs T(0) here
188 return T(0) + r;
189 }
190
191 template<typename T> T Math::tand(T x) {
192 static const T overflow = 1 / sq(numeric_limits<T>::epsilon());
193 T s, c;
194 sincosd(x, s, c);
195 // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf
196 T r = s / c; // special values from F.10.1.14
197 // With C++17 this becomes clamp(s / c, -overflow, overflow);
198 // Use max/min here (instead of fmax/fmin) to preserve NaN
199 return min(max(r, -overflow), overflow);
200 }
201
202 template<typename T> T Math::atan2d(T y, T x) {
203 // In order to minimize round-off errors, this function rearranges the
204 // arguments so that result of atan2 is in the range [-pi/4, pi/4] before
205 // converting it to degrees and mapping the result to the correct quadrant.
206 // With mpreal we could use T(mpfr::atan2u(y, x, td)); but we're not ready
207 // for this yet.
208 int q = 0;
209 if (fabs(y) > fabs(x)) { swap(x, y); q = 2; }
210 if (signbit(x)) { x = -x; ++q; }
211 // here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4]
212 // Replace atan2(y, x) / degree<T>() by this to ensure that special values
213 // (45, 90, etc.) are returned.
214 T ang = (atan2(y, x) / pi<T>()) * T(hd);
215 switch (q) {
216 case 1: ang = copysign(T(hd), y) - ang; break;
217 case 2: ang = qd - ang; break;
218 case 3: ang = -qd + ang; break;
219 default: break;
220 }
221 return ang;
222 }
223
224 template<typename T> T Math::atand(T x)
225 { return atan2d(x, T(1)); }
226
227 template<typename T> T Math::eatanhe(T x, T es) {
228 return es > 0 ? es * atanh(es * x) : -es * atan(es * x);
229 }
230
231 template<typename T> T Math::taupf(T tau, T es) {
232 // Need this test, otherwise tau = +/-inf gives taup = nan.
233 if (isfinite(tau)) {
234 T tau1 = hypot(T(1), tau),
235 sig = sinh( eatanhe(tau / tau1, es ) );
236 return hypot(T(1), sig) * tau - sig * tau1;
237 } else
238 return tau;
239 }
240
241 template<typename T> T Math::tauf(T taup, T es) {
242 static const int numit = 5;
243 // min iterations = 1, max iterations = 2; mean = 1.95
244 static const T tol = sqrt(numeric_limits<T>::epsilon()) / 10;
245 static const T taumax = 2 / sqrt(numeric_limits<T>::epsilon());
246 T e2m = 1 - sq(es),
247 // To lowest order in e^2, taup = (1 - e^2) * tau = _e2m * tau; so use
248 // tau = taup/e2m as a starting guess. Only 1 iteration is needed for
249 // |lat| < 3.35 deg, otherwise 2 iterations are needed. If, instead, tau
250 // = taup is used the mean number of iterations increases to 1.999 (2
251 // iterations are needed except near tau = 0).
252 //
253 // For large tau, taup = exp(-es*atanh(es)) * tau. Use this as for the
254 // initial guess for |taup| > 70 (approx |phi| > 89deg). Then for
255 // sufficiently large tau (such that sqrt(1+tau^2) = |tau|), we can exit
256 // with the intial guess and avoid overflow problems. This also reduces
257 // the mean number of iterations slightly from 1.963 to 1.954.
258 tau = fabs(taup) > 70 ? taup * exp(eatanhe(T(1), es)) : taup/e2m,
259 stol = tol * fmax(T(1), fabs(taup));
260 if (!(fabs(tau) < taumax)) return tau; // handles +/-inf and nan
261 for (int i = 0;
262 i < numit ||
263 GEOGRAPHICLIB_PANIC("Convergence failure in Math::tauf");
264 ++i) {
265 T taupa = taupf(tau, es),
266 dtau = (taup - taupa) * (1 + e2m * sq(tau)) /
267 ( e2m * hypot(T(1), tau) * hypot(T(1), taupa) );
268 tau += dtau;
269 if (!(fabs(dtau) >= stol))
270 break;
271 }
272 return tau;
273 }
274
275 template<typename T> T Math::hypot3(T x, T y, T z) {
276#if __cplusplus < 201703L || GEOGRAPHICLIB_PRECISION == 4
277 return sqrt(x*x + y*y + z*z);
278#else
279 return hypot(x, y, z);
280#endif
281 }
282
283 template<typename T> T Math::NaN() {
284#if defined(_MSC_VER)
285 return numeric_limits<T>::has_quiet_NaN ?
286 numeric_limits<T>::quiet_NaN() :
287 (numeric_limits<T>::max)();
288#else
289 return numeric_limits<T>::has_quiet_NaN ?
290 numeric_limits<T>::quiet_NaN() :
291 numeric_limits<T>::max();
292#endif
293 }
294
295 template<typename T> T Math::infinity() {
296#if defined(_MSC_VER)
297 return numeric_limits<T>::has_infinity ?
298 numeric_limits<T>::infinity() :
299 (numeric_limits<T>::max)();
300#else
301 return numeric_limits<T>::has_infinity ?
302 numeric_limits<T>::infinity() :
303 numeric_limits<T>::max();
304#endif
305 }
306
307 /// \cond SKIP
308 // Instantiate
309#define GEOGRAPHICLIB_MATH_INSTANTIATE(T) \
310 template T GEOGRAPHICLIB_EXPORT Math::sum <T>(T, T, T&); \
311 template T GEOGRAPHICLIB_EXPORT Math::AngNormalize <T>(T); \
312 template T GEOGRAPHICLIB_EXPORT Math::AngDiff <T>(T, T, T&); \
313 template T GEOGRAPHICLIB_EXPORT Math::AngRound <T>(T); \
314 template void GEOGRAPHICLIB_EXPORT Math::sincosd <T>(T, T&, T&); \
315 template void GEOGRAPHICLIB_EXPORT Math::sincosde <T>(T, T, T&, T&); \
316 template T GEOGRAPHICLIB_EXPORT Math::sind <T>(T); \
317 template T GEOGRAPHICLIB_EXPORT Math::cosd <T>(T); \
318 template T GEOGRAPHICLIB_EXPORT Math::tand <T>(T); \
319 template T GEOGRAPHICLIB_EXPORT Math::atan2d <T>(T, T); \
320 template T GEOGRAPHICLIB_EXPORT Math::atand <T>(T); \
321 template T GEOGRAPHICLIB_EXPORT Math::eatanhe <T>(T, T); \
322 template T GEOGRAPHICLIB_EXPORT Math::taupf <T>(T, T); \
323 template T GEOGRAPHICLIB_EXPORT Math::tauf <T>(T, T); \
324 template T GEOGRAPHICLIB_EXPORT Math::hypot3 <T>(T, T, T); \
325 template T GEOGRAPHICLIB_EXPORT Math::NaN <T>(); \
326 template T GEOGRAPHICLIB_EXPORT Math::infinity <T>();
327
328 // Instantiate with the standard floating type
329 GEOGRAPHICLIB_MATH_INSTANTIATE(float)
330 GEOGRAPHICLIB_MATH_INSTANTIATE(double)
331#if GEOGRAPHICLIB_HAVE_LONG_DOUBLE
332 // Instantiate if long double is distinct from double
333 GEOGRAPHICLIB_MATH_INSTANTIATE(long double)
334#endif
335#if GEOGRAPHICLIB_PRECISION > 3
336 // Instantiate with the high precision type
337 GEOGRAPHICLIB_MATH_INSTANTIATE(Math::real)
338#endif
339
340#undef GEOGRAPHICLIB_MATH_INSTANTIATE
341
342 // Also we need int versions for Utility::nummatch
343 template int GEOGRAPHICLIB_EXPORT Math::NaN <int>();
344 template int GEOGRAPHICLIB_EXPORT Math::infinity<int>();
345 /// \endcond
346
347} // namespace GeographicLib
GEOGRAPHICLIB_EXPORT
#define GEOGRAPHICLIB_EXPORT
Definition Constants.hpp:67
Math.hpp
Header for GeographicLib::Math class.
GEOGRAPHICLIB_VOLATILE
#define GEOGRAPHICLIB_VOLATILE
Definition Math.hpp:64
GEOGRAPHICLIB_PANIC
#define GEOGRAPHICLIB_PANIC(msg)
Definition Math.hpp:67
GEOGRAPHICLIB_PRECISION
#define GEOGRAPHICLIB_PRECISION
Definition Math.hpp:35
GeographicLib::Math::degree
static T degree()
Definition Math.hpp:215
GeographicLib::Math::tand
static T tand(T x)
Definition Math.cpp:191
GeographicLib::Math::sincosd
static void sincosd(T x, T &sinx, T &cosx)
Definition Math.cpp:104
GeographicLib::Math::atan2d
static T atan2d(T y, T x)
Definition Math.cpp:202
GeographicLib::Math::AngRound
static T AngRound(T x)
Definition Math.cpp:95
GeographicLib::Math::sq
static T sq(T x)
Definition Math.hpp:227
GeographicLib::Math::sum
static T sum(T u, T v, T &t)
Definition Math.cpp:55
GeographicLib::Math::sind
static T sind(T x)
Definition Math.cpp:160
GeographicLib::Math::qd
static constexpr int qd
degrees per quarter turn
Definition Math.hpp:150
GeographicLib::Math::tauf
static T tauf(T taup, T es)
Definition Math.cpp:241
GeographicLib::Math::hypot3
static T hypot3(T x, T y, T z)
Definition Math.cpp:275
GeographicLib::Math::AngNormalize
static T AngNormalize(T x)
Definition Math.cpp:69
GeographicLib::Math::digits10
static int digits10()
Definition Math.cpp:41
GeographicLib::Math::atand
static T atand(T x)
Definition Math.cpp:224
GeographicLib::Math::digits
static int digits()
Definition Math.cpp:20
GeographicLib::Math::infinity
static T infinity()
Definition Math.cpp:295
GeographicLib::Math::td
static constexpr int td
degrees per turn
Definition Math.hpp:154
GeographicLib::Math::sincosde
static void sincosde(T x, T t, T &sinx, T &cosx)
Definition Math.cpp:131
GeographicLib::Math::taupf
static T taupf(T tau, T es)
Definition Math.cpp:231
GeographicLib::Math::pi
static T pi()
Definition Math.hpp:205
GeographicLib::Math::NaN
static T NaN()
Definition Math.cpp:283
GeographicLib::Math::AngDiff
static T AngDiff(T x, T y, T &e)
Definition Math.cpp:80
GeographicLib::Math::hd
static constexpr int hd
degrees per half turn
Definition Math.hpp:153
GeographicLib::Math::eatanhe
static T eatanhe(T x, T es)
Definition Math.cpp:227
GeographicLib::Math::set_digits
static int set_digits(int ndigits)
Definition Math.cpp:28
GeographicLib::Math::real
double real
Definition Math.hpp:115
GeographicLib::Math::cosd
static T cosd(T x)
Definition Math.cpp:176
GeographicLib::Math::extra_digits
static int extra_digits()
Definition Math.cpp:49
GeographicLib
Namespace for GeographicLib.
Definition Accumulator.cpp:12
std::swap
void swap(GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &a, GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &b)
Definition NearestNeighbor.hpp:819