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