xref: /aosp_15_r20/external/angle/src/common/base/anglebase/numerics/clamped_math_impl.h (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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