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