Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Foundation/Math/Common.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. 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 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
126 return tmp >= numeric_limits<INT_TYPE>::max () ? numeric_limits<INT_TYPE>::max () : static_cast<INT_TYPE> (tmp);
127 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wimplicit-int-float-conversion\"");
128 }
129 else {
130 return tmp <= numeric_limits<INT_TYPE>::min () ? numeric_limits<INT_TYPE>::min () : static_cast<INT_TYPE> (tmp);
131 }
132 }
133 template <floating_point FLOAT_TYPE>
134 inline FLOAT_TYPE Round (FLOAT_TYPE n, unsigned int nDigitsOfPrecision)
135 {
136 using Common::StdCompat::isinf;
137 using Common::StdCompat::isnan;
138 Require (nDigitsOfPrecision >= 1);
139 if (isnan (n) or isinf (n)) [[unlikely]] {
140 return n;
141 }
142 auto absN = fabs (n);
143 unsigned int digitsBeforeDecimal = 0;
144 if (absN >= 1) {
145 digitsBeforeDecimal = Trunc<unsigned int> (log10 (absN)) + 1;
146 }
147 FLOAT_TYPE pow10Shifter = pow (10, static_cast<int> (nDigitsOfPrecision) - static_cast<int> (digitsBeforeDecimal));
148 return round (n * pow10Shifter) / pow10Shifter;
149 }
150
151 /*
152 ********************************************************************************
153 ******************************** Math::Trunc ***********************************
154 ********************************************************************************
155 */
156 namespace Private_ {
157 void ThrowTruncOfNAN_ ();
158 }
159 template <integral INT_TYPE, floating_point FLOAT_TYPE>
160 constexpr INT_TYPE Trunc (FLOAT_TYPE n)
161 {
162 using Common::StdCompat::isnan;
163 if (isnan (n)) [[unlikely]] {
164 Private_::ThrowTruncOfNAN_ ();
165 }
166 FLOAT_TYPE tmp = ::trunc (n);
167 if (tmp > 0) {
168 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
169 return tmp >= numeric_limits<INT_TYPE>::max () ? numeric_limits<INT_TYPE>::max () : static_cast<INT_TYPE> (tmp);
170 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wimplicit-int-float-conversion\"");
171 }
172 else {
173 return tmp <= numeric_limits<INT_TYPE>::min () ? numeric_limits<INT_TYPE>::min () : static_cast<INT_TYPE> (tmp);
174 }
175 }
176
177 /*
178 ********************************************************************************
179 **************************** Math::NearlyEquals ********************************
180 ********************************************************************************
181 */
182 template <Common::IBuiltinArithmetic T1, Common::IBuiltinArithmetic T2, typename EPSILON_TYPE>
183 constexpr bool NearlyEquals (T1 l, T2 r, EPSILON_TYPE epsilon)
184 {
185 using Common::StdCompat::isinf;
186 using Common::StdCompat::isnan;
187 if (isnan (l) or isnan (r)) [[unlikely]] {
188 return isnan (l) and isnan (r);
189 }
190 else if (isinf (l) or isinf (r)) [[unlikely]] {
191 // only 'equal' if inf and of same sign
192 // https://stackoverflow.com/questions/41834621/c-ieee-floats-inf-equal-inf
193 return l == r;
194 }
195 auto diff = l - r;
196 Require (epsilon >= 0); // other cases we ignore epsilon
197 Assert (not isnan (l) and not isnan (r) and not isinf (l) and not isinf (r));
198 return Abs (diff) <= epsilon;
199 }
200 template <Common::IBuiltinArithmetic T1, Common::IBuiltinArithmetic T2>
201 constexpr bool NearlyEquals (T1 l, T2 r)
202 {
203 using TC = common_type_t<T1, T2>;
204 if constexpr (floating_point<TC>) {
205 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
206 // \see https://realtimecollisiondetection.net/blog/?p=89
207 // using relTol = absTol
208 TC useEpsilon = kEpsilon_ * std::max<TC> ({static_cast<TC> (1.0), static_cast<TC> (Abs (l)), static_cast<TC> (Abs (r))});
209 return NearlyEquals (l, r, useEpsilon);
210 }
211 else {
212 return l == r;
213 }
214 }
215
216 /*
217 ********************************************************************************
218 ************************* Math::PinToSpecialPoint ******************************
219 ********************************************************************************
220 */
221 template <floating_point T>
222 constexpr T PinToSpecialPoint (T p, T special)
223 {
224 if (Math::NearlyEquals (p, special)) {
225 return special;
226 }
227 return p;
228 }
229 template <floating_point T>
230 constexpr T PinToSpecialPoint (T p, T special, T epsilon)
231 {
232 if (Math::NearlyEquals (p, special, epsilon)) {
233 return special;
234 }
235 return p;
236 }
237
238 /*
239 ********************************************************************************
240 ***************************** Math::PinInRange *********************************
241 ********************************************************************************
242 */
243 template <typename T>
244 [[deprecated ("Since Stroika v3.0d12 - use std::clamp")]] constexpr T PinInRange (T initialValue, T lowerBound, T upperBound)
245 {
246 Require (lowerBound <= upperBound);
247 auto r = max (lowerBound, min (upperBound, initialValue));
248 auto r2 = clamp (initialValue, lowerBound, upperBound);
249 Assert (r2 == r);
250 return r2;
251 }
252
253 /*
254 ********************************************************************************
255 ******************************** Math::AtLeast *********************************
256 ********************************************************************************
257 */
258 template <typename T>
259 constexpr T AtLeast (T initialValue, T lowerBound)
260 {
261 return max (initialValue, lowerBound);
262 }
263
264 /*
265 ********************************************************************************
266 ******************************** Math::AtMost **********************************
267 ********************************************************************************
268 */
269 template <typename T>
270 constexpr T AtMost (T initialValue, T upperBound)
271 {
272 return min (initialValue, upperBound);
273 }
274
275 /*
276 ********************************************************************************
277 ************************** Math::PinToMaxForType *******************************
278 ********************************************************************************
279 */
280 template <typename NEW_T, typename T>
281 constexpr NEW_T PinToMaxForType (T initialValue)
282 {
283 using LargerType = decltype (NEW_T{} + T{}); // maybe should use conditional<> for this?
284 return static_cast<NEW_T> (min<LargerType> (initialValue, numeric_limits<NEW_T>::max ()));
285 }
286
287 /*
288 ********************************************************************************
289 ********************************** Math::Abs ***********************************
290 ********************************************************************************
291 */
292 template <Common::IBuiltinArithmetic T, typename RESULT_TYPE>
293 constexpr RESULT_TYPE Abs (T v)
294 {
295 if constexpr (is_unsigned_v<T>) {
296 return v; // and no unary minus on an unsigned type, which MSVC warns about (C4146) even where never evaluated
297 }
298 else {
299 return v < 0 ? -v : v;
300 }
301 }
302
303 /*
304 ********************************************************************************
305 ********************************** Math::IsOdd *********************************
306 ********************************************************************************
307 */
308 template <integral T>
309 constexpr bool IsOdd (T v)
310 {
311 return v % 2 == 1;
312 }
313
314 /*
315 ********************************************************************************
316 ********************************* Math::IsEven *********************************
317 ********************************************************************************
318 */
319 template <integral T>
320 constexpr bool IsEven (T v)
321 {
322 static_assert (integral<T>);
323 return v % 2 == 0;
324 }
325
326 /*
327 ********************************************************************************
328 ******************************** Math::IsPrime *********************************
329 ********************************************************************************
330 */
331 template <integral T>
332 bool IsPrime (T v)
333 {
334 Require (v >= 0); // no negative numbers
335 // @todo - redo this as http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes but this is simpler and
336 // has no memory requirements and is good enuf for me to test...
337 if (v == 1) {
338 return false;
339 }
340 if (v == 2) {
341 return true; // special case
342 }
343 T checkUpTo = static_cast<T> (::sqrt (v)) + static_cast<T> (1);
344 // Check each number from 3 up to checkUpTo and see if its a divisor
345 for (T d = 2; d <= checkUpTo; ++d) {
346 if (v % d == 0) {
347 return false;
348 }
349 }
350 return true;
351 }
352
353 /*
354 ********************************************************************************
355 *************************** Math::PrimeAtLeastThisBig **************************
356 ********************************************************************************
357 */
358 template <integral T>
359 T PrimeAtLeastThisBig (T v)
360 {
361 while (not IsPrime (v)) {
362 ++v;
363 }
364 return v;
365 }
366
367}
constexpr bool isnan(T v) noexcept
Definition StdCompat.h:262