Speeduino
Loading...
Searching...
No Matches
maths.h
Go to the documentation of this file.
1#ifndef MATH_H
2#define MATH_H
3
4#include <stdint.h>
5#include <type_traits>
6#include <avr-fast-shift.h>
7#include <avr-fast-div.h>
8#ifdef USE_LIBDIVIDE
9// We use pre-computed constant parameters with libdivide where possible.
10// Using predefined constants saves flash and RAM (.bss) versus calling the
11// libdivide generator functions (E.g. libdivide_s32_gen)
12// 32-bit constants generated here: https://godbolt.org/z/vP8Kfejo9
13#include <libdivide.h>
14#endif
15#include "unit_testing.h"
16
17uint8_t random1to100(void) noexcept;
18
20static constexpr uint32_t MICROS_PER_SEC = UINT32_C(1000000);
21
23static constexpr uint32_t MICROS_PER_MIN = MICROS_PER_SEC*60U;
24
26static constexpr uint32_t MICROS_PER_HOUR = MICROS_PER_MIN*60U;
27
29static constexpr uint32_t MILLI_PER_SEC = MICROS_PER_SEC/1000;
30
32uint16_t pwmFreqToTicks(uint16_t frequency);
33
51#define DIV_ROUND_DOWN -1
52
54#define DIV_ROUND_UP 1
55
61#define DIV_ROUND_NEAREST 0
62
64#define DIV_ROUND_BEHAVIOR DIV_ROUND_NEAREST
65// (Unit tests expect DIV_ROUND_NEAREST behavior)
66
74#define DIV_ROUND_CORRECT(d, t) ((t)(((d)>>1U)+(t)DIV_ROUND_BEHAVIOR))
76
91#define DIV_ROUND_CLOSEST(n, d, t) ( \
92 (((n) < (t)(0)) ^ ((d) < (t)(0))) ? \
93 ((t)((n) - DIV_ROUND_CORRECT(d, t))/(t)(d)) : \
94 ((t)((n) + DIV_ROUND_CORRECT(d, t))/(t)(d)))
95
108#define UDIV_ROUND_CLOSEST(n, d, t) ((t)((n) + DIV_ROUND_CORRECT(d, t))/(t)(d))
109
117template <uint16_t divisor>
119 // This is a compile time version of UDIV_ROUND_CLOSEST
120 //
121 // As of avr-gcc 5.4.0, the compiler will optimize this to a multiply/shift
122 // assuming d is a constant.
123 return (uint16_t)((n + DIV_ROUND_CORRECT(divisor, uint16_t)) / divisor);
124}
125
127#define UDIV_ROUND_UP(n, d, t) ((t)((n) + (t)((d)+1U))/(t)(d))
128
130
132#define IS_INTEGER(d) ((d) == (int32_t)(d))
133
143static inline uint16_t div100(uint16_t n) {
144 // As of avr-gcc 5.4.0, the compiler will optimize this to a multiply/shift
145 // (unlike the signed integer overload, where __divmodhi4 is still called
146 // see https://godbolt.org/z/c5bs5noT1)
147#ifdef USE_LIBDIVIDE
148 constexpr libdivide::libdivide_u16_t libdiv_u16_100 = { .magic = 18351, .more = 70 };
149 // LCOV_EXCL_BR_START
150 return libdivide::libdivide_u16_do_raw(n + DIV_ROUND_CORRECT(UINT16_C(100), uint16_t), libdiv_u16_100.magic, libdiv_u16_100.more);
151 // LCOV_EXCL_BR_STOP
152#else
153 return UDIV_ROUND_CLOSEST(n, UINT16_C(100), uint16_t);
154#endif
155}
156
157static inline int16_t div100(int16_t n) {
158#ifdef USE_LIBDIVIDE
159 // Try faster unsigned path first
160 if (n>0) {
161 return div100((uint16_t)n);
162 }
163 // Negative values here, so adjust pre-division to get same
164 // behavior as roundf(float)
165 constexpr libdivide::libdivide_s16_t libdiv_s16_100 = { .magic = 20972, .more = 5 };
166 // LCOV_EXCL_BR_START
167 return libdivide::libdivide_s16_do_raw(n - DIV_ROUND_CORRECT(UINT16_C(100), uint16_t), libdiv_s16_100.magic, libdiv_s16_100.more);
168 // LCOV_EXCL_BR_STOP
169#else
170 return DIV_ROUND_CLOSEST(n, UINT16_C(100), int16_t);
171#endif
172}
173
174static inline uint32_t div100(uint32_t n) {
175#ifdef USE_LIBDIVIDE
176 if (n<=(uint32_t)UINT16_MAX) {
177 return div100((uint16_t)n);
178 }
179 constexpr libdivide::libdivide_u32_t libdiv_u32_100 = { .magic = 2748779070, .more = 6 };
180 // LCOV_EXCL_BR_START
181 return libdivide::libdivide_u32_do_raw(n + DIV_ROUND_CORRECT(UINT32_C(100), uint32_t), libdiv_u32_100.magic, libdiv_u32_100.more);
182 // LCOV_EXCL_BR_STOP
183#else
184 return UDIV_ROUND_CLOSEST(n, UINT32_C(100), uint32_t);
185#endif
186}
187
188static inline int32_t div100(int32_t n) {
189#ifdef USE_LIBDIVIDE
190 if (n<=INT16_MAX && n>=INT16_MIN) {
191 return div100((int16_t)n);
192 }
193 constexpr libdivide::libdivide_s32_t libdiv_s32_100 = { .magic = 1374389535, .more = 5 };
194 // LCOV_EXCL_BR_START
195 return libdivide::libdivide_s32_do_raw(n + (DIV_ROUND_CORRECT(UINT16_C(100), uint32_t) * (n<0 ? -1 : 1)), libdiv_s32_100.magic, libdiv_s32_100.more);
196 // LCOV_EXCL_BR_STOP
197#else
198 return DIV_ROUND_CLOSEST(n, INT32_C(100), int32_t);
199#endif
200}
202
209static inline uint32_t div360(uint32_t n) {
210#ifdef USE_LIBDIVIDE
211 constexpr libdivide::libdivide_u32_t libdiv_u32_360 = { .magic = 1813430637, .more = 72 };
212 // LCOV_EXCL_BR_START
213 return libdivide::libdivide_u32_do_raw(n + DIV_ROUND_CORRECT(UINT32_C(360), uint32_t), libdiv_u32_360.magic, libdiv_u32_360.more);
214 // LCOV_EXCL_BR_STOP
215#else
216 return (uint32_t)UDIV_ROUND_CLOSEST(n, UINT32_C(360), uint32_t);
217#endif
218}
219
232template <uint8_t b>
233static inline uint32_t rshift_round(uint32_t a) {
234 constexpr uint8_t CORRECTION_SHIFT = b-1U; // cppcheck-suppress misra-c2012-10.4
235 constexpr uint32_t CORRECTION = 1UL<<CORRECTION_SHIFT;
236 return rshift<b>((uint32_t)(a+CORRECTION));
237}
238
240
245template <uint8_t bitsPrecision>
246static inline uint32_t _percentageApprox(uint16_t percent, uint32_t value) {
247 uint16_t iPercent = div100((uint16_t)(percent << bitsPrecision));
248 return rshift_round<bitsPrecision>(value * (uint32_t)iPercent);
249}
250
252
272static inline uint32_t percentageApprox(uint16_t percent, uint32_t value) {
273 // To keep the percentage within 16-bits (for performance), we have to scale the precision based on the percentage.
274 // I.e. the larger the percentage, the smaller the precision has to be (and vice-versa).
275 //
276 // We could use __builtin_clz() and use the leading zero count as the precision, but that is slow:
277 // * AVR doesn't have a clz ASM instruction, so __builtin_clz() is implemented in software
278 // * It would require removing some compile time optimizations
279 #define TEST_AND_APPLY(precision) \
280 if (percent<(UINT16_C(1)<<(UINT16_C(16)-(precision)))) { \
281 return _percentageApprox<(precision)>(percent, value); \
282 }
283
284 TEST_AND_APPLY(9) // Percent<128
285 TEST_AND_APPLY(8) // Percent<256
286 TEST_AND_APPLY(7) // Percent<512
287 TEST_AND_APPLY(6) // Percent<1024
288
289 #undef TEST_AND_APPLY
290
291 // Percent<2048
292 return _percentageApprox<5U>(percent, value);
293}
294
298static inline uint32_t percentageApprox(uint8_t percent, uint32_t value) noexcept {
299 if (percent<(UINT8_C(1)<<UINT8_C(7))) {
300 return _percentageApprox<9U>(percent, value);
301 }
302 return _percentageApprox<8U>(percent, value);
303}
304
310static constexpr uint8_t ONE_HUNDRED_PCT = 100U;
311
318static inline uint32_t percentage(uint16_t percent, uint32_t value)
319{
320 return (uint32_t)div100((uint32_t)value * (uint32_t)percent);
321}
322
323
331static inline uint16_t halfPercentage(uint8_t percent, uint16_t value) {
332 uint32_t x200 = (uint32_t)percent * (uint32_t)value;
333#ifdef USE_LIBDIVIDE
334 constexpr libdivide::libdivide_u32_t libdiv_u32_200 = { .magic = 2748779070, .more = 7 };
335 // LCOV_EXCL_BR_START
336 return (uint16_t)libdivide::libdivide_u32_do_raw(x200 + DIV_ROUND_CORRECT(UINT32_C(200), uint32_t), libdiv_u32_200.magic, libdiv_u32_200.more);
337 // LCOV_EXCL_BR_STOP
338#else
339 return (uint16_t)UDIV_ROUND_CLOSEST(x200, UINT16_C(200), uint32_t);
340#endif
341}
342
350template <typename T>
351static inline T nudge(T min, T max, T value)
352{
353 T nudgeAmount = max - min;
354 if (value<min) { return value + nudgeAmount; }
355 if (value>=max) { return value - nudgeAmount; }
356 return value;
357}
358
368template <typename T>
369static inline T normalize(T min, T max, T value)
370{
371 T range = max - min;
372 while (value < min)
373 {
374 value += range;
375 }
376 while (value >= max)
377 {
378 value -= range;
379 }
380 return value;
381}
382
389template <typename TDividend, typename TDivisor>
390TESTABLE_STATIC_CONSTEXPR TDividend fast_div_closest(TDividend dividend, TDivisor divisor) {
391 return fast_div(dividend + DIV_ROUND_CORRECT(divisor, TDivisor), divisor);
392}
393
405// LCOV_EXCL_START
406template<class T>
407TESTABLE_STATIC_CONSTEXPR const T& clamp(const T& v, const T& lo, const T& hi){
408 return v<lo ? lo : hi<v ? hi : v;
409}
410// LCOV_EXCL_STOP
411
413
414template <typename T, typename TPrime>
415static inline T LOW_PASS_FILTER_8BIT(T input, uint8_t alpha, T prior) {
416 // Intermediate steps are for MISRA compliance
417 // Equivalent to: (input * (256 - alpha) + (prior * alpha)) >> 8
418 static constexpr uint16_t ALPHA_MAX_SHIFT = 8U;
419 static constexpr uint16_t ALPHA_MAX = 2U << (ALPHA_MAX_SHIFT-1U);
420 uint16_t inv_alpha = ALPHA_MAX - alpha;
421 TPrime prior_alpha = (prior * (TPrime)alpha);
422 TPrime preshift = (input * (TPrime)inv_alpha) + prior_alpha;
423 return (T)(preshift >> (TPrime)ALPHA_MAX_SHIFT);
424}
425
427
439static inline uint16_t LOW_PASS_FILTER(uint16_t input, uint8_t alpha, uint16_t prior) {
440 return LOW_PASS_FILTER_8BIT<uint16_t, uint32_t>(input, alpha, prior);
441}
442
444static inline int16_t LOW_PASS_FILTER(int16_t input, uint8_t alpha, int16_t prior) {
445 return LOW_PASS_FILTER_8BIT<int16_t, int32_t>(input, alpha, prior);
446}
447
459static inline uint8_t scale(const uint8_t from, const uint8_t fromRange, const uint8_t toRange) {
460 // Using uint16_t to avoid overflow when calculating the result
461 return fromRange==0U ? 0U : (((uint16_t)from * (uint16_t)toRange) / (uint16_t)fromRange);
462}
463
477static inline uint8_t fast_map(const uint8_t from, const uint8_t fromLow, const uint8_t fromHigh, const uint8_t toLow, const uint8_t toHigh) {
478 // Stick to unsigned math for performance, so need to check for output range inversion
479 if (toLow>toHigh) {
480 return toLow - scale(from - fromLow, fromHigh - fromLow, toLow-toHigh);
481 } else {
482 return scale(from - fromLow, fromHigh - fromLow, toHigh-toLow) + toLow;
483 }
484}
485
486#endif
#define DIV_ROUND_CORRECT(d, t)
Computes the denominator correction for rounding division based on our rounding behavior.
Definition maths.h:74
TESTABLE_STATIC_CONSTEXPR uint16_t div_round_closest_u16(uint16_t n)
Rounded unsigned integer division optimized for compile time constants.
Definition maths.h:118
#define DIV_ROUND_CLOSEST(n, d, t)
Rounded integer division.
Definition maths.h:91
#define UDIV_ROUND_CLOSEST(n, d, t)
Rounded unsigned integer division.
Definition maths.h:108
static uint8_t a
Definition maths.cpp:10
uint8_t random1to100(void) noexcept
Definition maths.cpp:11
static constexpr uint32_t MICROS_PER_MIN
Self-explanatory.
Definition maths.h:23
static T normalize(T min, T max, T value)
Correct value into the range [ min, max ) by adding or subtracting ( max- min )
Definition maths.h:369
static constexpr uint32_t MICROS_PER_SEC
Self-explanatory.
Definition maths.h:20
#define TEST_AND_APPLY(precision)
static constexpr uint32_t MILLI_PER_SEC
Self-explanatory.
Definition maths.h:29
static uint16_t LOW_PASS_FILTER(uint16_t input, uint8_t alpha, uint16_t prior)
Simple low pass IIR filter 16-bit values.
Definition maths.h:439
TESTABLE_STATIC_CONSTEXPR TDividend fast_div_closest(TDividend dividend, TDivisor divisor)
Same as fast_div(), except this will round to nearest integer instead of truncating.
Definition maths.h:390
static constexpr uint32_t MICROS_PER_HOUR
Self-explanatory.
Definition maths.h:26
uint16_t pwmFreqToTicks(uint16_t frequency)
Convert a frequency in Hz to the equivalent number of PWM timer ticks.
Definition maths.cpp:34
static uint32_t percentage(uint16_t percent, uint32_t value)
Integer based percentage calculation.
Definition maths.h:318
static uint32_t div360(uint32_t n)
Optimised integer division by 360.
Definition maths.h:209
static uint8_t scale(const uint8_t from, const uint8_t fromRange, const uint8_t toRange)
Scale a value from one range to another.
Definition maths.h:459
static uint8_t fast_map(const uint8_t from, const uint8_t fromLow, const uint8_t fromHigh, const uint8_t toLow, const uint8_t toHigh)
Specialist version of map(long, long, long, long, long) for performance.
Definition maths.h:477
static uint32_t percentageApprox(uint16_t percent, uint32_t value)
Integer based percentage calculation: faster, but less accurate, than percentage()
Definition maths.h:272
static constexpr uint8_t ONE_HUNDRED_PCT
This is only here to eliminate magic numbers.
Definition maths.h:310
static uint32_t rshift_round(uint32_t a)
Rounded arithmetic right shift.
Definition maths.h:233
TESTABLE_STATIC_CONSTEXPR const T & clamp(const T &v, const T &lo, const T &hi)
clamps a given value between the minimum and maximum thresholds.
Definition maths.h:407
static uint16_t halfPercentage(uint8_t percent, uint16_t value)
Integer based half-percentage calculation.
Definition maths.h:331
static T nudge(T min, T max, T value)
Make **one* pass at correcting the value into the range [ min, max)
Definition maths.h:351
static uint16_t div100(uint16_t n)
Performance optimised integer division by 100. I.e. same as n/100.
Definition maths.h:143
Unit testability support.
#define TESTABLE_STATIC_CONSTEXPR
Mark a function with constexpr, unless a unit test is in progress - then the entity is inlined.
Definition unit_testing.h:56