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 if constexpr (ClampedSubFastOp<T, U>::is_supported)
116 return ClampedSubFastOp<T, U>::template Do<V>(x, y);
117
118 static_assert(
119 std::is_same<V, result_type>::value || IsTypeInRangeForNumericType<U, V>::value,
120 "The saturation result cannot be determined from the "
121 "provided types.");
122 const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
123 V result = {};
124 return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result))) ? result : saturated;
125 }
126 };
127
128 template <typename T, typename U, class Enable = void>
129 struct ClampedMulOp
130 {};
131
132 template <typename T, typename U>
133 struct ClampedMulOp<
134 T,
135 U,
136 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
137 {
138 using result_type = typename MaxExponentPromotion<T, U>::type;
139 template <typename V = result_type>
140 static constexpr V Do(T x, U y)
141 {
142 if constexpr (ClampedMulFastOp<T, U>::is_supported)
143 return ClampedMulFastOp<T, U>::template Do<V>(x, y);
144
145 V result = {};
146 const V saturated = CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
147 return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result))) ? result : saturated;
148 }
149 };
150
151 template <typename T, typename U, class Enable = void>
152 struct ClampedDivOp
153 {};
154
155 template <typename T, typename U>
156 struct ClampedDivOp<
157 T,
158 U,
159 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
160 {
161 using result_type = typename MaxExponentPromotion<T, U>::type;
162 template <typename V = result_type>
163 static constexpr V Do(T x, U y)
164 {
165 V result = {};
166 if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result))))
167 return result;
168 // Saturation goes to max, min, or NaN (if x is zero).
169 return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
170 : SaturationDefaultLimits<V>::NaN();
171 }
172 };
173
174 template <typename T, typename U, class Enable = void>
175 struct ClampedModOp
176 {};
177
178 template <typename T, typename U>
179 struct ClampedModOp<
180 T,
181 U,
182 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
183 {
184 using result_type = typename MaxExponentPromotion<T, U>::type;
185 template <typename V = result_type>
186 static constexpr V Do(T x, U y)
187 {
188 V result = {};
189 return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result))) ? result : x;
190 }
191 };
192
193 template <typename T, typename U, class Enable = void>
194 struct ClampedLshOp
195 {};
196
197 // Left shift. Non-zero values saturate in the direction of the sign. A zero
198 // shifted by any value always results in zero.
199 template <typename T, typename U>
200 struct ClampedLshOp<
201 T,
202 U,
203 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
204 {
205 using result_type = T;
206 template <typename V = result_type>
207 static constexpr V Do(T x, U shift)
208 {
209 static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
210 if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits))
211 {
212 // Shift as unsigned to avoid undefined behavior.
213 V result = static_cast<V>(as_unsigned(x) << shift);
214 // If the shift can be reversed, we know it was valid.
215 if (BASE_NUMERICS_LIKELY(result >> shift == x))
216 return result;
217 }
218 return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
219 }
220 };
221
222 template <typename T, typename U, class Enable = void>
223 struct ClampedRshOp
224 {};
225
226 // Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
227 template <typename T, typename U>
228 struct ClampedRshOp<
229 T,
230 U,
231 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
232 {
233 using result_type = T;
234 template <typename V = result_type>
235 static constexpr V Do(T x, U shift)
236 {
237 static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
238 // Signed right shift is odd, because it saturates to -1 or 0.
239 const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
240 return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
241 ? saturated_cast<V>(x >> shift)
242 : saturated;
243 }
244 };
245
246 template <typename T, typename U, class Enable = void>
247 struct ClampedAndOp
248 {};
249
250 template <typename T, typename U>
251 struct ClampedAndOp<
252 T,
253 U,
254 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
255 {
256 using result_type =
257 typename std::make_unsigned<typename MaxExponentPromotion<T, U>::type>::type;
258 template <typename V>
259 static constexpr V Do(T x, U y)
260 {
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 ClampedOrOp
267 {};
268
269 // For simplicity we promote to unsigned integers.
270 template <typename T, typename U>
271 struct ClampedOrOp<
272 T,
273 U,
274 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
275 {
276 using result_type =
277 typename std::make_unsigned<typename MaxExponentPromotion<T, U>::type>::type;
278 template <typename V>
279 static constexpr V Do(T x, U y)
280 {
281 return static_cast<result_type>(x) | static_cast<result_type>(y);
282 }
283 };
284
285 template <typename T, typename U, class Enable = void>
286 struct ClampedXorOp
287 {};
288
289 // For simplicity we support only unsigned integers.
290 template <typename T, typename U>
291 struct ClampedXorOp<
292 T,
293 U,
294 typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type>
295 {
296 using result_type =
297 typename std::make_unsigned<typename MaxExponentPromotion<T, U>::type>::type;
298 template <typename V>
299 static constexpr V Do(T x, U y)
300 {
301 return static_cast<result_type>(x) ^ static_cast<result_type>(y);
302 }
303 };
304
305 template <typename T, typename U, class Enable = void>
306 struct ClampedMaxOp
307 {};
308
309 template <typename T, typename U>
310 struct ClampedMaxOp<
311 T,
312 U,
313 typename std::enable_if<std::is_arithmetic<T>::value && std::is_arithmetic<U>::value>::type>
314 {
315 using result_type = typename MaxExponentPromotion<T, U>::type;
316 template <typename V = result_type>
317 static constexpr V Do(T x, U y)
318 {
319 return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x) : saturated_cast<V>(y);
320 }
321 };
322
323 template <typename T, typename U, class Enable = void>
324 struct ClampedMinOp
325 {};
326
327 template <typename T, typename U>
328 struct ClampedMinOp<
329 T,
330 U,
331 typename std::enable_if<std::is_arithmetic<T>::value && std::is_arithmetic<U>::value>::type>
332 {
333 using result_type = typename LowestValuePromotion<T, U>::type;
334 template <typename V = result_type>
335 static constexpr V Do(T x, U y)
336 {
337 return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x) : saturated_cast<V>(y);
338 }
339 };
340
341 // This is just boilerplate that wraps the standard floating point arithmetic.
342 // A macro isn't the nicest solution, but it beats rewriting these repeatedly.
343 #define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
344 template <typename T, typename U> \
345 struct Clamped##NAME##Op<T, U, \
346 typename std::enable_if<std::is_floating_point<T>::value || \
347 std::is_floating_point<U>::value>::type> \
348 { \
349 using result_type = typename MaxExponentPromotion<T, U>::type; \
350 template <typename V = result_type> \
351 static constexpr V Do(T x, U y) \
352 { \
353 return saturated_cast<V>(x OP y); \
354 } \
355 };
356
357 BASE_FLOAT_ARITHMETIC_OPS(Add, +)
358 BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
359 BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
360 BASE_FLOAT_ARITHMETIC_OPS(Div, /)
361
362 #undef BASE_FLOAT_ARITHMETIC_OPS
363
364 } // namespace internal
365 } // namespace base
366 } // namespace angle
367
368 #endif // BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
369