MIP_SDK  v3.0.0-192-g8bd7933
MicroStrain Communications Library for embedded systems
saturating_math.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include <limits>
4 #include <type_traits>
5 #include <numeric>
6 
7 namespace microstrain
8 {
9 
10 #if __cpp_lib_saturation_arithmetic >= 202311L
11 
12 using std::add_sat;
13 using std::sub_sat;
14 using std::mul_sat;
15 using std::div_sat;
16 using std::saturate_cast;
17 
18 #else // __cpp_lib_saturation_arithmetic
19 
20 //
21 // Incomplete implementations of the std saturated arithmetic library from C++26.
22 // Currently, these only work with UNSIGNED integers.
23 //
24 
27 template<class T>
28 constexpr T add_sat(T x, T y) noexcept
29 {
30  static_assert(std::is_integral<T>::value, "Saturating arithmetic is only allowed on integral types");
31  static_assert(std::is_unsigned<T>::value, "Saturating arithmetic is not tested for signed types");
32 
33  constexpr T maximum = std::numeric_limits<T>::max();
34  constexpr T minimum = std::numeric_limits<T>::min();
35 
36  if constexpr(std::is_unsigned<T>::value)
37  {
38  return ((x + y) >= x) ? (x + y) : maximum; // If x+y overflows, return max
39  }
40  else // Signed
41  {
42  if(x >= 0)
43  {
44  if(y >= 0)
45  return (maximum - x <= y) ? (x+y) : maximum;
46  else
47  return x + y; // Adding opposite signs can never overflow
48  }
49  else // x < 0
50  {
51  if(y >= 0)
52  return x + y; // Adding opposite signs can never overflow
53  else // If the difference between x and min is less than -y, then x+y would underflow.
54  return (minimum - x <= y) ? (x+y) : minimum; // Same as (x - minium >= -y)
55  }
56  }
57 }
58 
61 template<class T>
62 constexpr T sub_sat(T x, T y) noexcept
63 {
64  static_assert(std::is_integral<T>::value, "Saturating arithmetic is only allowed on integral types");
65  static_assert(std::is_unsigned<T>::value, "Saturating arithmetic is not tested for signed types");
66 
67  constexpr T maximum = std::numeric_limits<T>::max();
68  constexpr T minimum = std::numeric_limits<T>::min();
69 
70  if constexpr(std::is_unsigned<T>::value)
71  {
72  return (x > y) ? (x-y) : 0u;
73  }
74  else // Signed
75  {
76  if(x >= 0)
77  {
78  if(y >= 0)
79  return x - y; // Subtracting same signs can never overflow
80  else // Negative y, so adding to x
81  return (-y >= maximum - x) ? (x - y) : maximum;
82  }
83  else // x < 0
84  {
85  if(y >= 0)
86  return (y <= minimum - x) ? (x - y) : minimum; // Same as (x - minium >= -y)
87  else // If the difference between x and min is less than -y, then x+y would underflow.
88  return x - y; // Subtracting same signs can never overflow
89  }
90  }
91 }
92 
93 
105 template<class T, class U>
106 constexpr T saturate_cast(U x) noexcept
107 {
108  static_assert(std::is_integral<T>::value, "T must be an integral type");
109  static_assert(std::is_integral<U>::value, "U must be an integral type");
110 
111  // This method takes advantage of the fact that comparing two same-signed values
112  // is done by first promoting the smaller type to the larger type.
113  // E.g. uint32_t(5) < uint64_t(10) is done as if both were uint64_t.
114  // If the result type T is smaller, bounds checks are needed.
115  // Trouble arises when the passed type U and result type T have differing signedness.
116  // In that case, comparisons or operations involving the two can cause the signed type
117  // to silently be converted to unsigned. Therefore, this function must check
118  // 3 cases: same-signedness, unsigned T but signed U, and signed T but unsigned U.
119  // See https://stackoverflow.com/q/47351654 and https://stackoverflow.com/a/46073296.
120 
121  // If both values have the same signedness
122  if constexpr(std::is_signed<T>::value == std::is_signed<U>::value)
123  {
124  // If result type T is the same or larger, then it can directly hold the value.
125  if constexpr(std::numeric_limits<T>::digits >= std::numeric_limits<U>::digits)
126  return x;
127  else // T is a smaller type and bounds checks are needed.
128  {
129  constexpr T minimum = std::numeric_limits<T>::min();
130  constexpr T maximum = std::numeric_limits<T>::max();
131 
132  if(x > static_cast<U>(maximum))
133  return maximum;
134 
135  // Only check minimum if signed, otherwise compiler may warn about unsigned comparison with 0 always false.
136  if constexpr(std::is_signed<U>::value)
137  {
138  if(x < static_cast<U>(minimum))
139  return minimum;
140  }
141 
142  return x;
143  }
144  }
145  // Unsigned result
146  else if constexpr(std::is_unsigned<T>::value)
147  {
148  static_assert(std::is_signed<U>::value); // Sanity check
149  using unsigned_U = typename std::make_unsigned<U>::type;
150 
151  constexpr T maximum = std::numeric_limits<T>::max();
152 
153  // Negative numbers are out of range for unsigned values.
154  if(x <= 0)
155  return 0;
156  // Positive numbers need a bounds check (if result T is a smaller type).
157  // X is positive, so safe to cast to unsigned and compare to maximum.
158  else if(static_cast<unsigned_U>(x) > maximum)
159  return maximum;
160  else
161  return x;
162  }
163  else // Signed result
164  {
165  static_assert(std::is_signed<T>::value); // Sanity check
166  static_assert(std::is_unsigned<U>::value); // Sanity check
167 
168  using unsigned_T = typename std::make_unsigned<T>::type;
169 
170  // Maximum value of (signed) T, represented as an unsigned value.
171  constexpr unsigned_T maximum = std::numeric_limits<T>::max();
172 
173  // Check upper limit since signed numbers have lower maximum.
174  // If T is larger, then x > maximum is false.
175  if(x > maximum)
176  return maximum;
177  else
178  return x;
179  }
180 }
181 
182 #endif // __cpp_lib_saturation_arithmetic
183 
184 
193 template<class T, class U>
194 constexpr void assign_sat(T& destination, U value)
195 {
196  destination = saturate_cast<T>(value);
197 }
198 
209 template<class T, class U>
210 constexpr T& accum_sat(T& counter, U amount)
211 {
212  return counter = add_sat<T>(counter, amount);
213 }
214 
225 template<class T, class U>
226 constexpr T& reduce_sat(T& counter, U amount)
227 {
228  return counter = sub_sat<T>(counter, amount);
229 }
230 
231 
237 template<class T>
239 {
240  static_assert(std::is_integral<T>::value(), "SaturatingInteger type must be integral");
241 
242  SaturatingInt() = default;
243  SaturatingInt(T value) : m_value(value) {}
244 
245  operator T() const { return m_value; }
246 
247  SaturatingInt& operator++() { if(m_value < std::numeric_limits<T>::max()) ++m_value; return *this; }
248  SaturatingInt operator++(int) { SaturatingInt tmp=*this; ++(*this); return tmp; }
249 
250  SaturatingInt& operator--() { if(m_value > std::numeric_limits<T>::min()) --m_value; return *this; }
251  SaturatingInt operator--(int) { SaturatingInt tmp=*this; --(*this); return tmp; }
252 
253  SaturatingInt& operator+(T value) { accum_sat(m_value, value); return *this; }
254  SaturatingInt& operator-(T value) { reduce_sat(m_value, value); return *this; }
255 
256 private:
257  T m_value = 0;
258 };
259 
260 
261 
262 } // namespace microstrain
microstrain::add_sat
constexpr T add_sat(T x, T y) noexcept
Definition: saturating_math.hpp:28
microstrain::accum_sat
constexpr T & accum_sat(T &counter, U amount)
Add a value to an accumulator and prevent it from overflowing.
Definition: saturating_math.hpp:210
microstrain::assign_sat
constexpr void assign_sat(T &destination, U value)
Assign an integral value to a destination variable, clamping its value to the range of the destinatio...
Definition: saturating_math.hpp:194
microstrain::saturate_cast
constexpr T saturate_cast(U x) noexcept
Convert an integral value to a different integral type while preventing overflow.
Definition: saturating_math.hpp:106
microstrain::reduce_sat
constexpr T & reduce_sat(T &counter, U amount)
Subtract a value from an accumulator and prevent it from overflowing.
Definition: saturating_math.hpp:226
microstrain::sub_sat
constexpr T sub_sat(T x, T y) noexcept
Definition: saturating_math.hpp:62
microstrain
Definition: embedded_time.h:8
microstrain::SaturatingInt
A class representing an integer that saturates instead of overflowing.
Definition: saturating_math.hpp:238