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