// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_NUMERICS_CLAMPED_MATH_IMPL_H_ #define BASE_NUMERICS_CLAMPED_MATH_IMPL_H_ #include <stddef.h> #include <stdint.h> #include <climits> #include <cmath> #include <cstdlib> #include <limits> #include <type_traits> #include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" #include "base/numerics/safe_math_shared_impl.h" namespace base { namespace internal { template <typename T, typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value>::type* = nullptr> constexpr T SaturatedNegWrapper(T value) { return MustTreatAsConstexpr(value) || !ClampedNegFastOp<T>::is_supported ? (NegateWrapper(value) != std::numeric_limits<T>::lowest() ? NegateWrapper(value) : std::numeric_limits<T>::max()) : ClampedNegFastOp<T>::Do(value); } template <typename T, typename std::enable_if<std::is_integral<T>::value && !std::is_signed<T>::value>::type* = nullptr> constexpr T SaturatedNegWrapper(T value) { return T(0); } template < typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr> constexpr T SaturatedNegWrapper(T value) { return -value; } template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr> constexpr T SaturatedAbsWrapper(T value) { // The calculation below is a static identity for unsigned types, but for // signed integer types it provides a non-branching, saturated absolute value. // This works because SafeUnsignedAbs() returns an unsigned type, which can // represent the absolute value of all negative numbers of an equal-width // integer type. The call to IsValueNegative() then detects overflow in the // special case of numeric_limits<T>::min(), by evaluating the bit pattern as // a signed integer value. If it is the overflow case, we end up subtracting // one from the unsigned result, thus saturating to numeric_limits<T>::max(). return static_cast<T>(SafeUnsignedAbs(value) - IsValueNegative<T>(SafeUnsignedAbs(value))); } template < typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr> constexpr T SaturatedAbsWrapper(T value) { return value < 0 ? -value : value; } template <typename T, typename U, class Enable = void> struct ClampedAddOp {}; template <typename T, typename U> struct ClampedAddOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename MaxExponentPromotion<T, U>::type; template <typename V = result_type> static constexpr V Do(T x, U y) { if (ClampedAddFastOp<T, U>::is_supported) return ClampedAddFastOp<T, U>::template Do<V>(x, y); static_assert(std::is_same<V, result_type>::value || IsTypeInRangeForNumericType<U, V>::value, "The saturation result cannot be determined from the " "provided types."); const V saturated = CommonMaxOrMin<V>(IsValueNegative(y)); V result = {}; return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result))) ? result : saturated; } }; template <typename T, typename U, class Enable = void> struct ClampedSubOp {}; template <typename T, typename U> struct ClampedSubOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename MaxExponentPromotion<T, U>::type; template <typename V = result_type> static constexpr V Do(T x, U y) { // TODO(jschuh) Make this "constexpr if" once we're C++17. if (ClampedSubFastOp<T, U>::is_supported) return ClampedSubFastOp<T, U>::template Do<V>(x, y); static_assert(std::is_same<V, result_type>::value || IsTypeInRangeForNumericType<U, V>::value, "The saturation result cannot be determined from the " "provided types."); const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y)); V result = {}; return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result))) ? result : saturated; } }; template <typename T, typename U, class Enable = void> struct ClampedMulOp {}; template <typename T, typename U> struct ClampedMulOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename MaxExponentPromotion<T, U>::type; template <typename V = result_type> static constexpr V Do(T x, U y) { // TODO(jschuh) Make this "constexpr if" once we're C++17. if (ClampedMulFastOp<T, U>::is_supported) return ClampedMulFastOp<T, U>::template Do<V>(x, y); V result = {}; const V saturated = CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y)); return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result))) ? result : saturated; } }; template <typename T, typename U, class Enable = void> struct ClampedDivOp {}; template <typename T, typename U> struct ClampedDivOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename MaxExponentPromotion<T, U>::type; template <typename V = result_type> static constexpr V Do(T x, U y) { V result = {}; if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result)))) return result; // Saturation goes to max, min, or NaN (if x is zero). return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y)) : SaturationDefaultLimits<V>::NaN(); } }; template <typename T, typename U, class Enable = void> struct ClampedModOp {}; template <typename T, typename U> struct ClampedModOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename MaxExponentPromotion<T, U>::type; template <typename V = result_type> static constexpr V Do(T x, U y) { V result = {}; return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result))) ? result : x; } }; template <typename T, typename U, class Enable = void> struct ClampedLshOp {}; // Left shift. Non-zero values saturate in the direction of the sign. A zero // shifted by any value always results in zero. template <typename T, typename U> struct ClampedLshOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = T; template <typename V = result_type> static constexpr V Do(T x, U shift) { static_assert(!std::is_signed<U>::value, "Shift value must be unsigned."); if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) { // Shift as unsigned to avoid undefined behavior. V result = static_cast<V>(as_unsigned(x) << shift); // If the shift can be reversed, we know it was valid. if (BASE_NUMERICS_LIKELY(result >> shift == x)) return result; } return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0; } }; template <typename T, typename U, class Enable = void> struct ClampedRshOp {}; // Right shift. Negative values saturate to -1. Positive or 0 saturates to 0. template <typename T, typename U> struct ClampedRshOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = T; template <typename V = result_type> static constexpr V Do(T x, U shift) { static_assert(!std::is_signed<U>::value, "Shift value must be unsigned."); // Signed right shift is odd, because it saturates to -1 or 0. const V saturated = as_unsigned(V(0)) - IsValueNegative(x); return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value) ? saturated_cast<V>(x >> shift) : saturated; } }; template <typename T, typename U, class Enable = void> struct ClampedAndOp {}; template <typename T, typename U> struct ClampedAndOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename std::make_unsigned< typename MaxExponentPromotion<T, U>::type>::type; template <typename V> static constexpr V Do(T x, U y) { return static_cast<result_type>(x) & static_cast<result_type>(y); } }; template <typename T, typename U, class Enable = void> struct ClampedOrOp {}; // For simplicity we promote to unsigned integers. template <typename T, typename U> struct ClampedOrOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename std::make_unsigned< typename MaxExponentPromotion<T, U>::type>::type; template <typename V> static constexpr V Do(T x, U y) { return static_cast<result_type>(x) | static_cast<result_type>(y); } }; template <typename T, typename U, class Enable = void> struct ClampedXorOp {}; // For simplicity we support only unsigned integers. template <typename T, typename U> struct ClampedXorOp<T, U, typename std::enable_if<std::is_integral<T>::value && std::is_integral<U>::value>::type> { using result_type = typename std::make_unsigned< typename MaxExponentPromotion<T, U>::type>::type; template <typename V> static constexpr V Do(T x, U y) { return static_cast<result_type>(x) ^ static_cast<result_type>(y); } }; template <typename T, typename U, class Enable = void> struct ClampedMaxOp {}; template <typename T, typename U> struct ClampedMaxOp< T, U, typename std::enable_if<std::is_arithmetic<T>::value && std::is_arithmetic<U>::value>::type> { using result_type = typename MaxExponentPromotion<T, U>::type; template <typename V = result_type> static constexpr V Do(T x, U y) { return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x) : saturated_cast<V>(y); } }; template <typename T, typename U, class Enable = void> struct ClampedMinOp {}; template <typename T, typename U> struct ClampedMinOp< T, U, typename std::enable_if<std::is_arithmetic<T>::value && std::is_arithmetic<U>::value>::type> { using result_type = typename LowestValuePromotion<T, U>::type; template <typename V = result_type> static constexpr V Do(T x, U y) { return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x) : saturated_cast<V>(y); } }; // This is just boilerplate that wraps the standard floating point arithmetic. // A macro isn't the nicest solution, but it beats rewriting these repeatedly. #define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \ template <typename T, typename U> \ struct Clamped##NAME##Op< \ T, U, \ typename std::enable_if<std::is_floating_point<T>::value || \ std::is_floating_point<U>::value>::type> { \ using result_type = typename MaxExponentPromotion<T, U>::type; \ template <typename V = result_type> \ static constexpr V Do(T x, U y) { \ return saturated_cast<V>(x OP y); \ } \ }; BASE_FLOAT_ARITHMETIC_OPS(Add, +) BASE_FLOAT_ARITHMETIC_OPS(Sub, -) BASE_FLOAT_ARITHMETIC_OPS(Mul, *) BASE_FLOAT_ARITHMETIC_OPS(Div, /) #undef BASE_FLOAT_ARITHMETIC_OPS } // namespace internal } // namespace base #endif // BASE_NUMERICS_CLAMPED_MATH_IMPL_H_