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