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