muldiv.h
Go to the documentation of this file.
1 /****
2  * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
3  * Created 2015 by Skurydin Alexey
4  * http://github.com/SmingHub/Sming
5  * All files of the Sming Core are provided under the LGPL v3 license.
6  *
7  * muldiv.h - Functions to perform unit conversion using unsigned integer arithmetic
8  *
9  * @author mikee47 <mike@sillyhouse.net>
10  *
11  * To perform conversion between different units requires the following calculation:
12  *
13  * result = value * ratio
14  *
15  * Where `ratio` would be our conversion factor, e.g. 0.04
16  *
17  * The main problem with floating point calculations is speed.
18  *
19  * Integer maths is much faster (unsigned integer maths are the fastest).
20  * Instead of 0.04 we'd use a rational fraction 4/100.
21  * generalised as num/den (or num:den, where num = numerator, den = denominator).
22  *
23  * The calculation then becomes:
24  *
25  * result = value * num / den
26  *
27  * However, the result would be truncated so a calculation like this:
28  *
29  * const unsigned value = 120;
30  * const unsigned num = 4;
31  * const unsigned den = 100;
32  *
33  * unsigned result = 120 * 4 / 100 = 480 / 100 = 4 (truncated) or 5 if rounded
34  *
35  * Generally we want the rounded result which we can do like this:
36  *
37  * result = round(value * num / den)
38  * = (value * num + 0.5) / den
39  * = ((value * num) + (den / 2)) / den
40  *
41  * Note that calculation range is limited by the multiplication (and the addition),
42  * so in practice we'd minimise the ratio to 1/25 in order to minimise overflow.
43  * The template versions of muldiv do this automatically as it doesn't cost us anything.
44  *
45  ****/
46 
47 #pragma once
48 
49 #include <limits>
50 #include <ratio>
51 #include <esp_attr.h>
52 #include <sming_attr.h>
53 
60 template <uint64_t num, uint64_t den, typename ValType> struct MuldivLimits {
66  static constexpr ValType overflow()
67  {
68  return std::numeric_limits<ValType>::max();
69  }
70 
75  static constexpr ValType maxValue()
76  {
77  return ({
78  using R = std::ratio<num, den>;
79  constexpr auto frac = R::den / 2;
80  (overflow() - frac) / R::num;
81  });
82  }
83 };
84 
93 template <typename ValType> __forceinline ValType muldivMaxValue(ValType timevar, ValType num, ValType den)
94 {
95  constexpr auto max = std::numeric_limits<ValType>::max();
96  auto frac = den / 2;
97  return ValType((max - frac) / num);
98 }
99 
109 template <typename ValType, typename NumDenType>
110 __forceinline ValType IRAM_ATTR muldiv(const ValType& value, const NumDenType& num, const NumDenType& den)
111 {
112  static_assert(std::is_unsigned<ValType>::value && std::is_unsigned<NumDenType>::value,
113  "muldiv types must be unsigned");
114 
115  if(value == 0 || num == 0) {
116  return 0;
117  }
118 
119  constexpr auto max = std::numeric_limits<ValType>::max();
120 
121  if(den == 0) {
122  return max;
123  }
124 
125  // Adds 0.5 to result to round value to nearest integer
126  auto frac = ValType(den / 2);
127 
128  /*
129  * calculation: result = ((value * num) + frac) / den
130  * overflow: ((value * num) + frac) > max
131  * or: value > (max - frac) / num
132  */
133  const ValType lim = (max - frac) / num;
134  if(value > lim) {
135  if(sizeof(ValType) < sizeof(uint64_t)) {
136  // Try using 64-bit calculation
137  return muldiv(uint64_t(value), num, den);
138  } else {
139  return max; // overflow
140  }
141  }
142 
143  auto mul = num * value;
144 
145  if(frac == 0) {
146  return mul;
147  }
148 
149  return (mul + frac) / den;
150 }
151 
160 __forceinline uint32_t IRAM_ATTR muldiv32(uint32_t value, uint32_t num, uint32_t den)
161 {
162  return muldiv(value, num, den);
163 }
164 
173 __forceinline uint64_t IRAM_ATTR muldiv64(const uint64_t& value, const uint64_t& num, const uint64_t& den)
174 {
175  return muldiv(value, num, den);
176 }
177 
186 template <uint64_t num, uint64_t den, typename ValType> __forceinline ValType IRAM_ATTR muldiv(const ValType& value)
187 {
188  static_assert(std::is_unsigned<ValType>::value, "muldiv types must be unsigned");
189 
190  // Minimise the fraction
191  using R = std::ratio<num, den>;
192  // Adds 0.5 to result to round value to nearest integer
193  constexpr ValType frac = R::den / 2;
194  constexpr ValType max = std::numeric_limits<ValType>::max();
195 
196  if(value == 0 || R::num == 0) {
197  return 0;
198  }
199 
200  // Probably already checked by std::ratio
201  if(R::den == 0) {
202  return max;
203  }
204 
205  /*
206  * calculation: result = ((value * num) + frac) / den
207  * overflow: ((value * num) + frac) > max
208  * or: value > (max - frac) / num
209  */
210 
211  constexpr ValType lim = (max - frac) / R::num;
212  if(value > lim) {
213  if(sizeof(ValType) < sizeof(uint64_t)) {
214  // Try using 64-bit calculation
215  return muldiv<R::num, R::den>(uint64_t(value));
216  } else {
217  return max; // overflow
218  }
219  }
220 
221  return ((value * ValType(R::num)) + frac) / ValType(R::den);
222 }
ValType muldivMaxValue(ValType timevar, ValType num, ValType den)
Get the maximum value which can be passed to muldiv() without overflowing.
Definition: muldiv.h:93
ValType muldiv(const ValType &value, const NumDenType &num, const NumDenType &den)
Perform muldiv using unsigned integer types.
Definition: muldiv.h:110
uint64_t muldiv64(const uint64_t &value, const uint64_t &num, const uint64_t &den)
Perform muldiv using 64-bit values.
Definition: muldiv.h:173
uint32_t muldiv32(uint32_t value, uint32_t num, uint32_t den)
Perform muldiv using 32-bit values.
Definition: muldiv.h:160
Obtain limits for a muldiv template calculation.
Definition: muldiv.h:60
static constexpr ValType maxValue()
Get the maximum value which can be used for a muldiv calculation without overflowing.
Definition: muldiv.h:75
static constexpr ValType overflow()
Get the value representing overflow for the given ValType.
Definition: muldiv.h:66