1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
6 #define BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
7 
8 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <climits>
12 #include <cmath>
13 #include <cstdlib>
14 #include <limits>
15 #include <type_traits>
16 
17 #include "base/numerics/checked_math.h"
18 #include "base/numerics/safe_conversions.h"
19 #include "base/numerics/safe_math_shared_impl.h"
20 
21 namespace base {
22 namespace internal {
23 
24 template <typename T,
25           typename std::enable_if<std::is_integral<T>::value &&
26                                   std::is_signed<T>::value>::type* = nullptr>
SaturatedNegWrapper(T value)27 constexpr T SaturatedNegWrapper(T value) {
28   return MustTreatAsConstexpr(value) || !ClampedNegFastOp<T>::is_supported
29              ? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
30                     ? NegateWrapper(value)
31                     : std::numeric_limits<T>::max())
32              : ClampedNegFastOp<T>::Do(value);
33 }
34 
35 template <typename T,
36           typename std::enable_if<std::is_integral<T>::value &&
37                                   !std::is_signed<T>::value>::type* = nullptr>
SaturatedNegWrapper(T value)38 constexpr T SaturatedNegWrapper(T value) {
39   return T(0);
40 }
41 
42 template <
43     typename T,
44     typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
SaturatedNegWrapper(T value)45 constexpr T SaturatedNegWrapper(T value) {
46   return -value;
47 }
48 
49 template <typename T,
50           typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
SaturatedAbsWrapper(T value)51 constexpr T SaturatedAbsWrapper(T value) {
52   // The calculation below is a static identity for unsigned types, but for
53   // signed integer types it provides a non-branching, saturated absolute value.
54   // This works because SafeUnsignedAbs() returns an unsigned type, which can
55   // represent the absolute value of all negative numbers of an equal-width
56   // integer type. The call to IsValueNegative() then detects overflow in the
57   // special case of numeric_limits<T>::min(), by evaluating the bit pattern as
58   // a signed integer value. If it is the overflow case, we end up subtracting
59   // one from the unsigned result, thus saturating to numeric_limits<T>::max().
60   return static_cast<T>(SafeUnsignedAbs(value) -
61                         IsValueNegative<T>(SafeUnsignedAbs(value)));
62 }
63 
64 template <
65     typename T,
66     typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
SaturatedAbsWrapper(T value)67 constexpr T SaturatedAbsWrapper(T value) {
68   return value < 0 ? -value : value;
69 }
70 
71 template <typename T, typename U, class Enable = void>
72 struct ClampedAddOp {};
73 
74 template <typename T, typename U>
75 struct ClampedAddOp<T,
76                     U,
77                     typename std::enable_if<std::is_integral<T>::value &&
78                                             std::is_integral<U>::value>::type> {
79   using result_type = typename MaxExponentPromotion<T, U>::type;
80   template <typename V = result_type>
81   static constexpr V Do(T x, U y) {
82     if (ClampedAddFastOp<T, U>::is_supported)
83       return ClampedAddFastOp<T, U>::template Do<V>(x, y);
84 
85     static_assert(std::is_same<V, result_type>::value ||
86                       IsTypeInRangeForNumericType<U, V>::value,
87                   "The saturation result cannot be determined from the "
88                   "provided types.");
89     const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
90     V result = {};
91     return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
92                ? result
93                : saturated;
94   }
95 };
96 
97 template <typename T, typename U, class Enable = void>
98 struct ClampedSubOp {};
99 
100 template <typename T, typename U>
101 struct ClampedSubOp<T,
102                     U,
103                     typename std::enable_if<std::is_integral<T>::value &&
104                                             std::is_integral<U>::value>::type> {
105   using result_type = typename MaxExponentPromotion<T, U>::type;
106   template <typename V = result_type>
107   static constexpr V Do(T x, U y) {
108     // TODO(jschuh) Make this "constexpr if" once we're C++17.
109     if (ClampedSubFastOp<T, U>::is_supported)
110       return ClampedSubFastOp<T, U>::template Do<V>(x, y);
111 
112     static_assert(std::is_same<V, result_type>::value ||
113                       IsTypeInRangeForNumericType<U, V>::value,
114                   "The saturation result cannot be determined from the "
115                   "provided types.");
116     const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
117     V result = {};
118     return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
119                ? result
120                : saturated;
121   }
122 };
123 
124 template <typename T, typename U, class Enable = void>
125 struct ClampedMulOp {};
126 
127 template <typename T, typename U>
128 struct ClampedMulOp<T,
129                     U,
130                     typename std::enable_if<std::is_integral<T>::value &&
131                                             std::is_integral<U>::value>::type> {
132   using result_type = typename MaxExponentPromotion<T, U>::type;
133   template <typename V = result_type>
134   static constexpr V Do(T x, U y) {
135     // TODO(jschuh) Make this "constexpr if" once we're C++17.
136     if (ClampedMulFastOp<T, U>::is_supported)
137       return ClampedMulFastOp<T, U>::template Do<V>(x, y);
138 
139     V result = {};
140     const V saturated =
141         CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
142     return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
143                ? result
144                : saturated;
145   }
146 };
147 
148 template <typename T, typename U, class Enable = void>
149 struct ClampedDivOp {};
150 
151 template <typename T, typename U>
152 struct ClampedDivOp<T,
153                     U,
154                     typename std::enable_if<std::is_integral<T>::value &&
155                                             std::is_integral<U>::value>::type> {
156   using result_type = typename MaxExponentPromotion<T, U>::type;
157   template <typename V = result_type>
158   static constexpr V Do(T x, U y) {
159     V result = {};
160     if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result))))
161       return result;
162     // Saturation goes to max, min, or NaN (if x is zero).
163     return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
164              : SaturationDefaultLimits<V>::NaN();
165   }
166 };
167 
168 template <typename T, typename U, class Enable = void>
169 struct ClampedModOp {};
170 
171 template <typename T, typename U>
172 struct ClampedModOp<T,
173                     U,
174                     typename std::enable_if<std::is_integral<T>::value &&
175                                             std::is_integral<U>::value>::type> {
176   using result_type = typename MaxExponentPromotion<T, U>::type;
177   template <typename V = result_type>
178   static constexpr V Do(T x, U y) {
179     V result = {};
180     return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
181                ? result
182                : x;
183   }
184 };
185 
186 template <typename T, typename U, class Enable = void>
187 struct ClampedLshOp {};
188 
189 // Left shift. Non-zero values saturate in the direction of the sign. A zero
190 // shifted by any value always results in zero.
191 template <typename T, typename U>
192 struct ClampedLshOp<T,
193                     U,
194                     typename std::enable_if<std::is_integral<T>::value &&
195                                             std::is_integral<U>::value>::type> {
196   using result_type = T;
197   template <typename V = result_type>
198   static constexpr V Do(T x, U shift) {
199     static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
200     if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
201       // Shift as unsigned to avoid undefined behavior.
202       V result = static_cast<V>(as_unsigned(x) << shift);
203       // If the shift can be reversed, we know it was valid.
204       if (BASE_NUMERICS_LIKELY(result >> shift == x))
205         return result;
206     }
207     return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
208   }
209 };
210 
211 template <typename T, typename U, class Enable = void>
212 struct ClampedRshOp {};
213 
214 // Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
215 template <typename T, typename U>
216 struct ClampedRshOp<T,
217                     U,
218                     typename std::enable_if<std::is_integral<T>::value &&
219                                             std::is_integral<U>::value>::type> {
220   using result_type = T;
221   template <typename V = result_type>
222   static constexpr V Do(T x, U shift) {
223     static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
224     // Signed right shift is odd, because it saturates to -1 or 0.
225     const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
226     return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
227                ? saturated_cast<V>(x >> shift)
228                : saturated;
229   }
230 };
231 
232 template <typename T, typename U, class Enable = void>
233 struct ClampedAndOp {};
234 
235 template <typename T, typename U>
236 struct ClampedAndOp<T,
237                     U,
238                     typename std::enable_if<std::is_integral<T>::value &&
239                                             std::is_integral<U>::value>::type> {
240   using result_type = typename std::make_unsigned<
241       typename MaxExponentPromotion<T, U>::type>::type;
242   template <typename V>
243   static constexpr V Do(T x, U y) {
244     return static_cast<result_type>(x) & static_cast<result_type>(y);
245   }
246 };
247 
248 template <typename T, typename U, class Enable = void>
249 struct ClampedOrOp {};
250 
251 // For simplicity we promote to unsigned integers.
252 template <typename T, typename U>
253 struct ClampedOrOp<T,
254                    U,
255                    typename std::enable_if<std::is_integral<T>::value &&
256                                            std::is_integral<U>::value>::type> {
257   using result_type = typename std::make_unsigned<
258       typename MaxExponentPromotion<T, U>::type>::type;
259   template <typename V>
260   static constexpr V Do(T x, U y) {
261     return static_cast<result_type>(x) | static_cast<result_type>(y);
262   }
263 };
264 
265 template <typename T, typename U, class Enable = void>
266 struct ClampedXorOp {};
267 
268 // For simplicity we support only unsigned integers.
269 template <typename T, typename U>
270 struct ClampedXorOp<T,
271                     U,
272                     typename std::enable_if<std::is_integral<T>::value &&
273                                             std::is_integral<U>::value>::type> {
274   using result_type = typename std::make_unsigned<
275       typename MaxExponentPromotion<T, U>::type>::type;
276   template <typename V>
277   static constexpr V Do(T x, U y) {
278     return static_cast<result_type>(x) ^ static_cast<result_type>(y);
279   }
280 };
281 
282 template <typename T, typename U, class Enable = void>
283 struct ClampedMaxOp {};
284 
285 template <typename T, typename U>
286 struct ClampedMaxOp<
287     T,
288     U,
289     typename std::enable_if<std::is_arithmetic<T>::value &&
290                             std::is_arithmetic<U>::value>::type> {
291   using result_type = typename MaxExponentPromotion<T, U>::type;
292   template <typename V = result_type>
293   static constexpr V Do(T x, U y) {
294     return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
295                                        : saturated_cast<V>(y);
296   }
297 };
298 
299 template <typename T, typename U, class Enable = void>
300 struct ClampedMinOp {};
301 
302 template <typename T, typename U>
303 struct ClampedMinOp<
304     T,
305     U,
306     typename std::enable_if<std::is_arithmetic<T>::value &&
307                             std::is_arithmetic<U>::value>::type> {
308   using result_type = typename LowestValuePromotion<T, U>::type;
309   template <typename V = result_type>
310   static constexpr V Do(T x, U y) {
311     return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
312                                     : saturated_cast<V>(y);
313   }
314 };
315 
316 // This is just boilerplate that wraps the standard floating point arithmetic.
317 // A macro isn't the nicest solution, but it beats rewriting these repeatedly.
318 #define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                              \
319   template <typename T, typename U>                                      \
320   struct Clamped##NAME##Op<                                              \
321       T, U,                                                              \
322       typename std::enable_if<std::is_floating_point<T>::value ||        \
323                               std::is_floating_point<U>::value>::type> { \
324     using result_type = typename MaxExponentPromotion<T, U>::type;       \
325     template <typename V = result_type>                                  \
326     static constexpr V Do(T x, U y) {                                    \
327       return saturated_cast<V>(x OP y);                                  \
328     }                                                                    \
329   };
330 
331 BASE_FLOAT_ARITHMETIC_OPS(Add, +)
332 BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
333 BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
334 BASE_FLOAT_ARITHMETIC_OPS(Div, /)
335 
336 #undef BASE_FLOAT_ARITHMETIC_OPS
337 
338 }  // namespace internal
339 }  // namespace base
340 
341 #endif  // BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
342