Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Foundation/Math/Common.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include <cmath>
5#include <cstdlib>
6
7#include "Stroika/Foundation/Common/Concepts.h"
10
11namespace Stroika::Foundation::Math {
12
13 /*
14 ********************************************************************************
15 ************************************ Math::nan *********************************
16 ********************************************************************************
17 */
18 template <floating_point T>
19 constexpr T nan ()
20 {
21 return numeric_limits<T>::quiet_NaN ();
22 }
23
24 /*
25 ********************************************************************************
26 ******************************* Math::infinity *********************************
27 ********************************************************************************
28 */
29 template <floating_point T>
30 constexpr T infinity ()
31 {
32 return numeric_limits<T>::infinity ();
33 }
34
35 /*
36 ********************************************************************************
37 ****************************** Math::RoundUpTo *********************************
38 ********************************************************************************
39 */
40 namespace Private {
41 template <unsigned_integral T>
42 constexpr T RoundUpTo_UnSignedHelper_ (T x, T toNearest)
43 {
44 return (((x + toNearest - 1u) / toNearest) * toNearest);
45 }
46 template <unsigned_integral T>
47 constexpr T RoundDownTo_UnSignedHelper_ (T x, T toNearest)
48 {
49 return ((x / toNearest) * toNearest);
50 }
51 template <signed_integral T>
52 constexpr T RoundUpTo_SignedHelper_ (T x, T toNearest)
53 {
54 using UNSIGNED_T = make_unsigned_t<T>;
55 Require (toNearest > 0);
56 if (x < 0) {
57 return (-static_cast<T> (RoundDownTo_UnSignedHelper_ (static_cast<UNSIGNED_T> (-x), static_cast<UNSIGNED_T> (toNearest))));
58 }
59 else {
60 return static_cast<T> (RoundUpTo_UnSignedHelper_<UNSIGNED_T> (x, toNearest));
61 }
62 }
63 template <signed_integral T>
64 constexpr T RoundDownTo_SignedHelper_ (T x, T toNearest)
65 {
66 using UNSIGNED_T = make_unsigned_t<T>;
67 Require (toNearest > 0);
68 if (x < 0) {
69 return (-static_cast<T> (RoundUpTo_UnSignedHelper_ (static_cast<UNSIGNED_T> (-x), static_cast<UNSIGNED_T> (toNearest))));
70 }
71 else {
72 return (RoundDownTo_UnSignedHelper_ (static_cast<UNSIGNED_T> (x), static_cast<UNSIGNED_T> (toNearest)));
73 }
74 }
75 template <signed_integral T>
76 constexpr T RoundUpTo_ (T x, T toNearest)
77 {
78 return Private::RoundUpTo_SignedHelper_<T> (x, toNearest);
79 }
80 template <unsigned_integral T>
81 constexpr T RoundUpTo_ (T x, T toNearest)
82 {
83 return Private::RoundUpTo_UnSignedHelper_<T> (x, toNearest);
84 }
85 template <signed_integral T>
86 constexpr T RoundDownTo_ (T x, T toNearest)
87 {
88 return Private::RoundDownTo_SignedHelper_<T> (x, toNearest);
89 }
90 template <unsigned_integral T>
91 constexpr T RoundDownTo_ (T x, T toNearest)
92 {
93 return Private::RoundDownTo_UnSignedHelper_<T> (x, toNearest);
94 }
95 }
96 template <Common::IBuiltinArithmetic T>
97 constexpr T RoundUpTo (T x, T toNearest)
98 {
99 // @todo could simplify with if constexpr in this function, and lose the helpers above
100 return Private::RoundUpTo_ (x, toNearest);
101 }
102
103 /*
104 ********************************************************************************
105 **************************** Math::RoundDownTo *********************************
106 ********************************************************************************
107 */
108 template <Common::IBuiltinArithmetic T>
109 constexpr T RoundDownTo (T x, T toNearest)
110 {
111 // @todo could simplify with if constexpr in this function, and lose the helpers above
112 return Private::RoundDownTo_ (x, toNearest);
113 }
114
115 /*
116 ********************************************************************************
117 ********************************* Math::Round **********************************
118 ********************************************************************************
119 */
120 template <integral INT_TYPE, floating_point FLOAT_TYPE>
121 constexpr INT_TYPE Round (FLOAT_TYPE n)
122 {
123 FLOAT_TYPE tmp = ::round (n);
124 if (tmp > 0) {
125#if (defined(__clang_major__) && !defined(__APPLE__) && (__clang_major__ >= 10)) || \
126 (defined(__clang_major__) && defined(__APPLE__) && (__clang_major__ >= 13))
127 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wimplicit-int-float-conversion\""); // warning: implicit conversion from 'std::__1::numeric_limits<long>::type' (aka 'long') to 'double' changes value from 9223372036854775807 to 9223372036854775808
128#endif
129 return tmp >= numeric_limits<INT_TYPE>::max () ? numeric_limits<INT_TYPE>::max () : static_cast<INT_TYPE> (tmp);
130#if (defined(__clang_major__) && !defined(__APPLE__) && (__clang_major__ >= 10)) || \
131 (defined(__clang_major__) && defined(__APPLE__) && (__clang_major__ >= 13))
132 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wimplicit-int-float-conversion\"");
133#endif
134 }
135 else {
136 return tmp <= numeric_limits<INT_TYPE>::min () ? numeric_limits<INT_TYPE>::min () : static_cast<INT_TYPE> (tmp);
137 }
138 }
139 template <floating_point FLOAT_TYPE>
140 inline FLOAT_TYPE Round (FLOAT_TYPE n, unsigned int nDigitsOfPrecision)
141 {
142 using Common::StdCompat::isinf;
143 using Common::StdCompat::isnan;
144 Require (nDigitsOfPrecision >= 1);
145 if (isnan (n) or isinf (n)) [[unlikely]] {
146 return n;
147 }
148 auto absN = fabs (n);
149 unsigned int digitsBeforeDecimal = 0;
150 if (absN >= 1) {
151 digitsBeforeDecimal = Trunc<unsigned int> (log10 (absN)) + 1;
152 }
153 FLOAT_TYPE pow10Shifter = pow (10, static_cast<int> (nDigitsOfPrecision) - static_cast<int> (digitsBeforeDecimal));
154 return round (n * pow10Shifter) / pow10Shifter;
155 }
156
157 /*
158 ********************************************************************************
159 ******************************** Math::Trunc ***********************************
160 ********************************************************************************
161 */
162 namespace Private_ {
163 void ThrowTruncOfNAN_ ();
164 }
165 template <integral INT_TYPE, floating_point FLOAT_TYPE>
166 constexpr INT_TYPE Trunc (FLOAT_TYPE n)
167 {
168 using Common::StdCompat::isnan;
169 if (isnan (n)) [[unlikely]] {
170 Private_::ThrowTruncOfNAN_ ();
171 }
172 FLOAT_TYPE tmp = ::trunc (n);
173 if (tmp > 0) {
174#if (defined(__clang_major__) && !defined(__APPLE__) && (__clang_major__ >= 10)) || \
175 (defined(__clang_major__) && defined(__APPLE__) && (__clang_major__ >= 13))
176 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wimplicit-int-float-conversion\""); // warning: implicit conversion from 'std::__1::numeric_limits<long>::type' (aka 'long') to 'double' changes value from 9223372036854775807 to 9223372036854775808
177#endif
178 return tmp >= numeric_limits<INT_TYPE>::max () ? numeric_limits<INT_TYPE>::max () : static_cast<INT_TYPE> (tmp);
179#if (defined(__clang_major__) && !defined(__APPLE__) && (__clang_major__ >= 10)) || \
180 (defined(__clang_major__) && defined(__APPLE__) && (__clang_major__ >= 13))
181 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wimplicit-int-float-conversion\"");
182#endif
183 }
184 else {
185 return tmp <= numeric_limits<INT_TYPE>::min () ? numeric_limits<INT_TYPE>::min () : static_cast<INT_TYPE> (tmp);
186 }
187 }
188
189 /*
190 ********************************************************************************
191 **************************** Math::NearlyEquals ********************************
192 ********************************************************************************
193 */
194 template <Common::IBuiltinArithmetic T1, Common::IBuiltinArithmetic T2, typename EPSILON_TYPE>
195 constexpr bool NearlyEquals (T1 l, T2 r, EPSILON_TYPE epsilon)
196 {
197 using Common::StdCompat::isinf;
198 using Common::StdCompat::isnan;
199 if (isnan (l) or isnan (r)) [[unlikely]] {
200 return isnan (l) and isnan (r);
201 }
202 else if (isinf (l) or isinf (r)) [[unlikely]] {
203 // only 'equal' if inf and of same sign
204 // https://stackoverflow.com/questions/41834621/c-ieee-floats-inf-equal-inf
205 return l == r;
206 }
207 auto diff = l - r;
208 Require (epsilon >= 0); // other cases we ignore epsilon
209 Assert (not isnan (l) and not isnan (r) and not isinf (l) and not isinf (r));
210 return Abs (diff) <= epsilon;
211 }
212 template <Common::IBuiltinArithmetic T1, Common::IBuiltinArithmetic T2>
213 constexpr bool NearlyEquals (T1 l, T2 r)
214 {
215 using TC = common_type_t<T1, T2>;
216 if constexpr (floating_point<TC>) {
217 constexpr TC kEpsilon_ = 10000 * numeric_limits<TC>::epsilon (); // pick more than epsilon cuz some math functions have more error than a single bit... - even 1000x not enuf sometimes
218 // \see https://realtimecollisiondetection.net/blog/?p=89
219 // using relTol = absTol
220 TC useEpsilon = kEpsilon_ * std::max<TC> ({static_cast<TC> (1.0), static_cast<TC> (Abs (l)), static_cast<TC> (Abs (r))});
221 return NearlyEquals (l, r, useEpsilon);
222 }
223 else {
224 return l == r;
225 }
226 }
227
228 /*
229 ********************************************************************************
230 ************************* Math::PinToSpecialPoint ******************************
231 ********************************************************************************
232 */
233 template <floating_point T>
234 constexpr T PinToSpecialPoint (T p, T special)
235 {
236 if (Math::NearlyEquals (p, special)) {
237 return special;
238 }
239 return p;
240 }
241 template <floating_point T>
242 constexpr T PinToSpecialPoint (T p, T special, T epsilon)
243 {
244 if (Math::NearlyEquals (p, special, epsilon)) {
245 return special;
246 }
247 return p;
248 }
249
250 /*
251 ********************************************************************************
252 ***************************** Math::PinInRange *********************************
253 ********************************************************************************
254 */
255 template <typename T>
256 [[deprecated ("Since Stroika v3.0d12 - use std::clamp")]] constexpr T PinInRange (T initialValue, T lowerBound, T upperBound)
257 {
258 Require (lowerBound <= upperBound);
259 auto r = max (lowerBound, min (upperBound, initialValue));
260 auto r2 = clamp (initialValue, lowerBound, upperBound);
261 Assert (r2 == r);
262 return r2;
263 }
264
265 /*
266 ********************************************************************************
267 ******************************** Math::AtLeast *********************************
268 ********************************************************************************
269 */
270 template <typename T>
271 constexpr T AtLeast (T initialValue, T lowerBound)
272 {
273 return max (initialValue, lowerBound);
274 }
275
276 /*
277 ********************************************************************************
278 ******************************** Math::AtMost **********************************
279 ********************************************************************************
280 */
281 template <typename T>
282 constexpr T AtMost (T initialValue, T upperBound)
283 {
284 return min (initialValue, upperBound);
285 }
286
287 /*
288 ********************************************************************************
289 ************************** Math::PinToMaxForType *******************************
290 ********************************************************************************
291 */
292 template <typename NEW_T, typename T>
293 constexpr NEW_T PinToMaxForType (T initialValue)
294 {
295 using LargerType = decltype (NEW_T{} + T{}); // maybe should use conditional<> for this?
296 return static_cast<NEW_T> (min<LargerType> (initialValue, numeric_limits<NEW_T>::max ()));
297 }
298
299 /*
300 ********************************************************************************
301 ********************************** Math::Abs ***********************************
302 ********************************************************************************
303 */
304 template <Common::IBuiltinArithmetic T, typename RESULT_TYPE>
305 constexpr RESULT_TYPE Abs (T v)
306 {
307#if __cplusplus >= kStrokia_Foundation_Common_cplusplus_23
308 if constexpr (Common::IAnyOf<T, int, intmax_t>) {
309 return std::abs (v);
310 }
311 else if constexpr (Common::IAnyOf<T, long>) {
312 return std::labs (v);
313 }
314 else if constexpr (Common::IAnyOf<T, long long>) {
315 return std::llabs (v);
316 }
317#endif
318 return v < 0 ? -v : v;
319 }
320
321 /*
322 ********************************************************************************
323 ********************************** Math::IsOdd *********************************
324 ********************************************************************************
325 */
326 template <integral T>
327 constexpr bool IsOdd (T v)
328 {
329 return v % 2 == 1;
330 }
331
332 /*
333 ********************************************************************************
334 ********************************* Math::IsEven *********************************
335 ********************************************************************************
336 */
337 template <integral T>
338 constexpr bool IsEven (T v)
339 {
340 static_assert (integral<T>);
341 return v % 2 == 0;
342 }
343
344 /*
345 ********************************************************************************
346 ******************************** Math::IsPrime *********************************
347 ********************************************************************************
348 */
349 template <integral T>
350 bool IsPrime (T v)
351 {
352 Require (v >= 0); // no negative numbers
353 // @todo - redo this as http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes but this is simpler and
354 // has no memory requirements and is good enuf for me to test...
355 if (v == 1) {
356 return false;
357 }
358 if (v == 2) {
359 return true; // special case
360 }
361 T checkUpTo = static_cast<T> (::sqrt (v)) + static_cast<T> (1);
362 // Check each number from 3 up to checkUpTo and see if its a divisor
363 for (T d = 2; d <= checkUpTo; ++d) {
364 if (v % d == 0) {
365 return false;
366 }
367 }
368 return true;
369 }
370
371}
constexpr bool isnan(T v) noexcept
Definition StdCompat.h:168