// Copyright 2017 The Chromium Authors // 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_ // IWYU pragma: private, include "base/numerics/clamped_math.h" #include #include #include #include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" #include "base/numerics/safe_math_shared_impl.h" // IWYU pragma: export namespace base { namespace internal { template requires(std::signed_integral) constexpr T SaturatedNegWrapper(T value) { return IsConstantEvaluated() || !ClampedNegFastOp::is_supported ? (NegateWrapper(value) != std::numeric_limits::lowest() ? NegateWrapper(value) : std::numeric_limits::max()) : ClampedNegFastOp::Do(value); } template requires(std::unsigned_integral) constexpr T SaturatedNegWrapper(T value) { return T(0); } template requires(std::floating_point) constexpr T SaturatedNegWrapper(T value) { return -value; } template requires(std::integral) 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::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::max(). return static_cast( SafeUnsignedAbs(value) - IsValueNegative(static_cast(SafeUnsignedAbs(value)))); } template requires(std::floating_point) constexpr T SaturatedAbsWrapper(T value) { return value < 0 ? -value : value; } template struct ClampedAddOp {}; template requires(std::integral && std::integral) struct ClampedAddOp { using result_type = typename MaxExponentPromotion::type; template static constexpr V Do(T x, U y) { if (!IsConstantEvaluated() && ClampedAddFastOp::is_supported) return ClampedAddFastOp::template Do(x, y); static_assert(std::is_same_v || IsTypeInRangeForNumericType::value, "The saturation result cannot be determined from the " "provided types."); const V saturated = CommonMaxOrMin(IsValueNegative(y)); V result = {}; if (CheckedAddOp::Do(x, y, &result)) [[likely]] { return result; } return saturated; } }; template struct ClampedSubOp {}; template requires(std::integral && std::integral) struct ClampedSubOp { using result_type = typename MaxExponentPromotion::type; template static constexpr V Do(T x, U y) { if (!IsConstantEvaluated() && ClampedSubFastOp::is_supported) return ClampedSubFastOp::template Do(x, y); static_assert(std::is_same_v || IsTypeInRangeForNumericType::value, "The saturation result cannot be determined from the " "provided types."); const V saturated = CommonMaxOrMin(!IsValueNegative(y)); V result = {}; if (CheckedSubOp::Do(x, y, &result)) [[likely]] { return result; } return saturated; } }; template struct ClampedMulOp {}; template requires(std::integral && std::integral) struct ClampedMulOp { using result_type = typename MaxExponentPromotion::type; template static constexpr V Do(T x, U y) { if (!IsConstantEvaluated() && ClampedMulFastOp::is_supported) return ClampedMulFastOp::template Do(x, y); V result = {}; const V saturated = CommonMaxOrMin(IsValueNegative(x) ^ IsValueNegative(y)); if (CheckedMulOp::Do(x, y, &result)) [[likely]] { return result; } return saturated; } }; template struct ClampedDivOp {}; template requires(std::integral && std::integral) struct ClampedDivOp { using result_type = typename MaxExponentPromotion::type; template static constexpr V Do(T x, U y) { V result = {}; if ((CheckedDivOp::Do(x, y, &result))) [[likely]] { return result; } // Saturation goes to max, min, or NaN (if x is zero). return x ? CommonMaxOrMin(IsValueNegative(x) ^ IsValueNegative(y)) : SaturationDefaultLimits::NaN(); } }; template struct ClampedModOp {}; template requires(std::integral && std::integral) struct ClampedModOp { using result_type = typename MaxExponentPromotion::type; template static constexpr V Do(T x, U y) { V result = {}; if (CheckedModOp::Do(x, y, &result)) [[likely]] { return result; } return x; } }; template 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 requires(std::integral && std::integral) struct ClampedLshOp { using result_type = T; template static constexpr V Do(T x, U shift) { static_assert(!std::is_signed_v, "Shift value must be unsigned."); if (shift < std::numeric_limits::digits) [[likely]] { // Shift as unsigned to avoid undefined behavior. V result = static_cast(as_unsigned(x) << shift); // If the shift can be reversed, we know it was valid. if (result >> shift == x) [[likely]] { return result; } } return x ? CommonMaxOrMin(IsValueNegative(x)) : 0; } }; template struct ClampedRshOp {}; // Right shift. Negative values saturate to -1. Positive or 0 saturates to 0. template requires(std::integral && std::integral) struct ClampedRshOp { using result_type = T; template static constexpr V Do(T x, U shift) { static_assert(!std::is_signed_v, "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); if (shift < IntegerBitsPlusSign::value) [[likely]] { return saturated_cast(x >> shift); } return saturated; } }; template struct ClampedAndOp {}; template requires(std::integral && std::integral) struct ClampedAndOp { using result_type = typename std::make_unsigned< typename MaxExponentPromotion::type>::type; template static constexpr V Do(T x, U y) { return static_cast(x) & static_cast(y); } }; template struct ClampedOrOp {}; // For simplicity we promote to unsigned integers. template requires(std::integral && std::integral) struct ClampedOrOp { using result_type = typename std::make_unsigned< typename MaxExponentPromotion::type>::type; template static constexpr V Do(T x, U y) { return static_cast(x) | static_cast(y); } }; template struct ClampedXorOp {}; // For simplicity we support only unsigned integers. template requires(std::integral && std::integral) struct ClampedXorOp { using result_type = typename std::make_unsigned< typename MaxExponentPromotion::type>::type; template static constexpr V Do(T x, U y) { return static_cast(x) ^ static_cast(y); } }; template struct ClampedMaxOp {}; template requires(std::is_arithmetic_v && std::is_arithmetic_v) struct ClampedMaxOp { using result_type = typename MaxExponentPromotion::type; template static constexpr V Do(T x, U y) { return IsGreater::Test(x, y) ? saturated_cast(x) : saturated_cast(y); } }; template struct ClampedMinOp {}; template requires(std::is_arithmetic_v && std::is_arithmetic_v) struct ClampedMinOp { using result_type = typename LowestValuePromotion::type; template static constexpr V Do(T x, U y) { return IsLess::Test(x, y) ? saturated_cast(x) : saturated_cast(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 \ requires(std::floating_point || std::floating_point) \ struct Clamped##NAME##Op { \ using result_type = typename MaxExponentPromotion::type; \ template \ static constexpr V Do(T x, U y) { \ return saturated_cast(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_