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