| // Copyright 2014 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_SAFE_CONVERSIONS_IMPL_H_ |
| #define BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ |
| |
| // IWYU pragma: private, include "base/numerics/safe_conversions.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <concepts> |
| #include <limits> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "base/numerics/integral_constant_like.h" |
| |
| namespace base::internal { |
| |
| // The std library doesn't provide a binary max_exponent for integers, however |
| // we can compute an analog using std::numeric_limits<>::digits. |
| template <typename NumericType> |
| inline constexpr int kMaxExponent = |
| std::is_floating_point_v<NumericType> |
| ? std::numeric_limits<NumericType>::max_exponent |
| : std::numeric_limits<NumericType>::digits + 1; |
| |
| // The number of bits (including the sign) in an integer. Eliminates sizeof |
| // hacks. |
| template <typename NumericType> |
| inline constexpr int kIntegerBitsPlusSign = |
| std::numeric_limits<NumericType>::digits + std::is_signed_v<NumericType>; |
| |
| // Determines if a numeric value is negative without throwing compiler |
| // warnings on: unsigned(value) < 0. |
| template <typename T> |
| requires(std::is_arithmetic_v<T>) |
| constexpr bool IsValueNegative(T value) { |
| if constexpr (std::is_signed_v<T>) { |
| return value < 0; |
| } else { |
| return false; |
| } |
| } |
| |
| // This performs a fast negation, returning a signed value. It works on unsigned |
| // arguments, but probably doesn't do what you want for any unsigned value |
| // larger than max / 2 + 1 (i.e. signed min cast to unsigned). |
| template <typename T> |
| requires std::is_integral_v<T> |
| constexpr auto ConditionalNegate(T x, bool is_negative) { |
| using SignedT = std::make_signed_t<T>; |
| using UnsignedT = std::make_unsigned_t<T>; |
| return static_cast<SignedT>((static_cast<UnsignedT>(x) ^ |
| static_cast<UnsignedT>(-SignedT(is_negative))) + |
| is_negative); |
| } |
| |
| // This performs a safe, absolute value via unsigned overflow. |
| template <typename T> |
| requires std::is_integral_v<T> |
| constexpr auto SafeUnsignedAbs(T value) { |
| using UnsignedT = std::make_unsigned_t<T>; |
| return IsValueNegative(value) |
| ? static_cast<UnsignedT>(0u - static_cast<UnsignedT>(value)) |
| : static_cast<UnsignedT>(value); |
| } |
| |
| // TODO(jschuh): Debug builds don't reliably propagate constants, so we restrict |
| // some accelerated runtime paths to release builds until this can be forced |
| // with consteval support in C++20 or C++23. |
| #if defined(NDEBUG) |
| inline constexpr bool kEnableAsmCode = true; |
| #else |
| inline constexpr bool kEnableAsmCode = false; |
| #endif |
| |
| // Forces a crash, like a NOTREACHED(). Used for numeric boundary errors. |
| // Also used in a constexpr template to trigger a compilation failure on |
| // an error condition. |
| struct CheckOnFailure { |
| template <typename T> |
| static T HandleFailure() { |
| #if defined(_MSC_VER) |
| __debugbreak(); |
| #elif defined(__GNUC__) || defined(__clang__) |
| __builtin_trap(); |
| #else |
| ((void)(*(volatile char*)0 = 0)); |
| #endif |
| return T(); |
| } |
| }; |
| |
| enum class IntegerRepresentation { kUnsigned, kSigned }; |
| |
| // A range for a given nunmeric Src type is contained for a given numeric Dst |
| // type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and |
| // numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true. |
| // We implement this as template specializations rather than simple static |
| // comparisons to ensure type correctness in our comparisons. |
| enum class NumericRangeRepresentation { kNotContained, kContained }; |
| |
| // Helper templates to statically determine if our destination type can contain |
| // maximum and minimum values represented by the source type. |
| |
| // Default case, used for same sign: Dst is guaranteed to contain Src only if |
| // its range is equal or larger. |
| template <typename Dst, |
| typename Src, |
| IntegerRepresentation DstSign = |
| std::is_signed_v<Dst> ? IntegerRepresentation::kSigned |
| : IntegerRepresentation::kUnsigned, |
| IntegerRepresentation SrcSign = |
| std::is_signed_v<Src> ? IntegerRepresentation::kSigned |
| : IntegerRepresentation::kUnsigned> |
| inline constexpr auto kStaticDstRangeRelationToSrcRange = |
| kMaxExponent<Dst> >= kMaxExponent<Src> |
| ? NumericRangeRepresentation::kContained |
| : NumericRangeRepresentation::kNotContained; |
| |
| // Unsigned to signed: Dst is guaranteed to contain source only if its range is |
| // larger. |
| template <typename Dst, typename Src> |
| inline constexpr auto |
| kStaticDstRangeRelationToSrcRange<Dst, |
| Src, |
| IntegerRepresentation::kSigned, |
| IntegerRepresentation::kUnsigned> = |
| kMaxExponent<Dst> > kMaxExponent<Src> |
| ? NumericRangeRepresentation::kContained |
| : NumericRangeRepresentation::kNotContained; |
| |
| // Signed to unsigned: Dst cannot be statically determined to contain Src. |
| template <typename Dst, typename Src> |
| inline constexpr auto |
| kStaticDstRangeRelationToSrcRange<Dst, |
| Src, |
| IntegerRepresentation::kUnsigned, |
| IntegerRepresentation::kSigned> = |
| NumericRangeRepresentation::kNotContained; |
| |
| // This class wraps the range constraints as separate booleans so the compiler |
| // can identify constants and eliminate unused code paths. |
| class RangeCheck { |
| public: |
| constexpr RangeCheck() = default; |
| constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound) |
| : is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {} |
| |
| constexpr bool operator==(const RangeCheck& rhs) const = default; |
| |
| constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; } |
| constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; } |
| constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; } |
| constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; } |
| constexpr bool IsOverflowFlagSet() const { return is_overflow_; } |
| constexpr bool IsUnderflowFlagSet() const { return is_underflow_; } |
| |
| private: |
| // Do not change the order of these member variables. The integral conversion |
| // optimization depends on this exact order. |
| const bool is_underflow_ = false; |
| const bool is_overflow_ = false; |
| }; |
| |
| // The following helper template addresses a corner case in range checks for |
| // conversion from a floating-point type to an integral type of smaller range |
| // but larger precision (e.g. float -> unsigned). The problem is as follows: |
| // 1. Integral maximum is always one less than a power of two, so it must be |
| // truncated to fit the mantissa of the floating point. The direction of |
| // rounding is implementation defined, but by default it's always IEEE |
| // floats, which round to nearest and thus result in a value of larger |
| // magnitude than the integral value. |
| // Example: float f = UINT_MAX; // f is 4294967296f but UINT_MAX |
| // // is 4294967295u. |
| // 2. If the floating point value is equal to the promoted integral maximum |
| // value, a range check will erroneously pass. |
| // Example: (4294967296f <= 4294967295u) // This is true due to a precision |
| // // loss in rounding up to float. |
| // 3. When the floating point value is then converted to an integral, the |
| // resulting value is out of range for the target integral type and |
| // thus is implementation defined. |
| // Example: unsigned u = (float)INT_MAX; // u will typically overflow to 0. |
| // To fix this bug we manually truncate the maximum value when the destination |
| // type is an integral of larger precision than the source floating-point type, |
| // such that the resulting maximum is represented exactly as a floating point. |
| template <typename Dst, typename Src, template <typename> class Bounds> |
| struct NarrowingRange { |
| using SrcLimits = std::numeric_limits<Src>; |
| using DstLimits = std::numeric_limits<Dst>; |
| |
| // Computes the mask required to make an accurate comparison between types. |
| static constexpr int kShift = (kMaxExponent<Src> > kMaxExponent<Dst> && |
| SrcLimits::digits < DstLimits::digits) |
| ? (DstLimits::digits - SrcLimits::digits) |
| : 0; |
| |
| template <typename T> |
| requires(std::same_as<T, Dst> && |
| ((std::integral<T> && kShift < DstLimits::digits) || |
| (std::floating_point<T> && kShift == 0))) |
| // Masks out the integer bits that are beyond the precision of the |
| // intermediate type used for comparison. |
| static constexpr T Adjust(T value) { |
| if constexpr (std::integral<T>) { |
| using UnsignedDst = typename std::make_unsigned_t<T>; |
| return static_cast<T>( |
| ConditionalNegate(SafeUnsignedAbs(value) & |
| ~((UnsignedDst{1} << kShift) - UnsignedDst{1}), |
| IsValueNegative(value))); |
| } else { |
| return value; |
| } |
| } |
| |
| static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); } |
| static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); } |
| }; |
| |
| // The following templates are for ranges that must be verified at runtime. We |
| // split it into checks based on signedness to avoid confusing casts and |
| // compiler warnings on signed an unsigned comparisons. |
| |
| // Default case, used for same sign narrowing: The range is contained for normal |
| // limits. |
| template <typename Dst, |
| typename Src, |
| template <typename> |
| class Bounds, |
| IntegerRepresentation DstSign = |
| std::is_signed_v<Dst> ? IntegerRepresentation::kSigned |
| : IntegerRepresentation::kUnsigned, |
| IntegerRepresentation SrcSign = |
| std::is_signed_v<Src> ? IntegerRepresentation::kSigned |
| : IntegerRepresentation::kUnsigned, |
| NumericRangeRepresentation DstRange = |
| kStaticDstRangeRelationToSrcRange<Dst, Src>> |
| struct DstRangeRelationToSrcRangeImpl { |
| static constexpr RangeCheck Check(Src value) { |
| using SrcLimits = std::numeric_limits<Src>; |
| using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| return RangeCheck( |
| static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() || |
| static_cast<Dst>(value) >= DstLimits::lowest(), |
| static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() || |
| static_cast<Dst>(value) <= DstLimits::max()); |
| } |
| }; |
| |
| // Signed to signed narrowing: Both the upper and lower boundaries may be |
| // exceeded for standard limits. |
| template <typename Dst, typename Src, template <typename> class Bounds> |
| struct DstRangeRelationToSrcRangeImpl< |
| Dst, |
| Src, |
| Bounds, |
| IntegerRepresentation::kSigned, |
| IntegerRepresentation::kSigned, |
| NumericRangeRepresentation::kNotContained> { |
| static constexpr RangeCheck Check(Src value) { |
| using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max()); |
| } |
| }; |
| |
| // Unsigned to unsigned narrowing: Only the upper bound can be exceeded for |
| // standard limits. |
| template <typename Dst, typename Src, template <typename> class Bounds> |
| struct DstRangeRelationToSrcRangeImpl< |
| Dst, |
| Src, |
| Bounds, |
| IntegerRepresentation::kUnsigned, |
| IntegerRepresentation::kUnsigned, |
| NumericRangeRepresentation::kNotContained> { |
| static constexpr RangeCheck Check(Src value) { |
| using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| return RangeCheck( |
| DstLimits::lowest() == Dst{0} || value >= DstLimits::lowest(), |
| value <= DstLimits::max()); |
| } |
| }; |
| |
| // Unsigned to signed: Only the upper bound can be exceeded for standard limits. |
| template <typename Dst, typename Src, template <typename> class Bounds> |
| struct DstRangeRelationToSrcRangeImpl< |
| Dst, |
| Src, |
| Bounds, |
| IntegerRepresentation::kSigned, |
| IntegerRepresentation::kUnsigned, |
| NumericRangeRepresentation::kNotContained> { |
| static constexpr RangeCheck Check(Src value) { |
| using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| using Promotion = decltype(Src() + Dst()); |
| return RangeCheck(DstLimits::lowest() <= Dst{0} || |
| static_cast<Promotion>(value) >= |
| static_cast<Promotion>(DstLimits::lowest()), |
| static_cast<Promotion>(value) <= |
| static_cast<Promotion>(DstLimits::max())); |
| } |
| }; |
| |
| // Signed to unsigned: The upper boundary may be exceeded for a narrower Dst, |
| // and any negative value exceeds the lower boundary for standard limits. |
| template <typename Dst, typename Src, template <typename> class Bounds> |
| struct DstRangeRelationToSrcRangeImpl< |
| Dst, |
| Src, |
| Bounds, |
| IntegerRepresentation::kUnsigned, |
| IntegerRepresentation::kSigned, |
| NumericRangeRepresentation::kNotContained> { |
| static constexpr RangeCheck Check(Src value) { |
| using SrcLimits = std::numeric_limits<Src>; |
| using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| using Promotion = decltype(Src() + Dst()); |
| bool ge_zero; |
| // Converting floating-point to integer will discard fractional part, so |
| // values in (-1.0, -0.0) will truncate to 0 and fit in Dst. |
| if constexpr (std::is_floating_point_v<Src>) { |
| ge_zero = value > Src{-1}; |
| } else { |
| ge_zero = value >= Src{0}; |
| } |
| return RangeCheck( |
| ge_zero && (DstLimits::lowest() == 0 || |
| static_cast<Dst>(value) >= DstLimits::lowest()), |
| static_cast<Promotion>(SrcLimits::max()) <= |
| static_cast<Promotion>(DstLimits::max()) || |
| static_cast<Promotion>(value) <= |
| static_cast<Promotion>(DstLimits::max())); |
| } |
| }; |
| |
| // Simple wrapper for statically checking if a type's range is contained. |
| template <typename Dst, typename Src> |
| inline constexpr bool kIsTypeInRangeForNumericType = |
| kStaticDstRangeRelationToSrcRange<Dst, Src> == |
| NumericRangeRepresentation::kContained; |
| |
| template <typename Dst, |
| template <typename> class Bounds = std::numeric_limits, |
| typename Src> |
| requires(std::is_arithmetic_v<Src> && std::is_arithmetic_v<Dst> && |
| Bounds<Dst>::lowest() < Bounds<Dst>::max()) |
| constexpr RangeCheck DstRangeRelationToSrcRange(Src value) { |
| return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value); |
| } |
| |
| // Integer promotion templates used by the portable checked integer arithmetic. |
| template <size_t Size, bool IsSigned> |
| struct IntegerForDigitsAndSignImpl; |
| |
| #define INTEGER_FOR_DIGITS_AND_SIGN(I) \ |
| template <> \ |
| struct IntegerForDigitsAndSignImpl<kIntegerBitsPlusSign<I>, \ |
| std::is_signed_v<I>> { \ |
| using type = I; \ |
| } |
| |
| INTEGER_FOR_DIGITS_AND_SIGN(int8_t); |
| INTEGER_FOR_DIGITS_AND_SIGN(uint8_t); |
| INTEGER_FOR_DIGITS_AND_SIGN(int16_t); |
| INTEGER_FOR_DIGITS_AND_SIGN(uint16_t); |
| INTEGER_FOR_DIGITS_AND_SIGN(int32_t); |
| INTEGER_FOR_DIGITS_AND_SIGN(uint32_t); |
| INTEGER_FOR_DIGITS_AND_SIGN(int64_t); |
| INTEGER_FOR_DIGITS_AND_SIGN(uint64_t); |
| #undef INTEGER_FOR_DIGITS_AND_SIGN |
| |
| template <size_t Size, bool IsSigned> |
| using IntegerForDigitsAndSign = |
| IntegerForDigitsAndSignImpl<Size, IsSigned>::type; |
| |
| // WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to |
| // support 128-bit math, then the ArithmeticPromotion template below will need |
| // to be updated (or more likely replaced with a decltype expression). |
| static_assert(kIntegerBitsPlusSign<intmax_t> == 64, |
| "Max integer size not supported for this toolchain."); |
| |
| template <typename Integer, bool IsSigned = std::is_signed_v<Integer>> |
| using TwiceWiderInteger = |
| IntegerForDigitsAndSign<kIntegerBitsPlusSign<Integer> * 2, IsSigned>; |
| |
| // Determines the type that can represent the largest positive value. |
| template <typename Lhs, typename Rhs> |
| using MaxExponentPromotion = |
| std::conditional_t<(kMaxExponent<Lhs> > kMaxExponent<Rhs>), Lhs, Rhs>; |
| |
| // Determines the type that can represent the lowest arithmetic value. |
| template <typename Lhs, typename Rhs> |
| using LowestValuePromotion = std::conditional_t< |
| std::is_signed_v<Lhs> |
| ? (!std::is_signed_v<Rhs> || kMaxExponent<Lhs> > kMaxExponent<Rhs>) |
| : (!std::is_signed_v<Rhs> && kMaxExponent<Lhs> < kMaxExponent<Rhs>), |
| Lhs, |
| Rhs>; |
| |
| // Determines the type that is best able to represent an arithmetic result. |
| |
| // Default case, used when the side with the max exponent is big enough. |
| template <typename Lhs, |
| typename Rhs = Lhs, |
| bool is_intmax_type = |
| std::is_integral_v<MaxExponentPromotion<Lhs, Rhs>> && |
| kIntegerBitsPlusSign<MaxExponentPromotion<Lhs, Rhs>> == |
| kIntegerBitsPlusSign<intmax_t>, |
| bool is_max_exponent = |
| kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>, |
| Lhs> == |
| NumericRangeRepresentation::kContained && |
| kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>, |
| Rhs> == |
| NumericRangeRepresentation::kContained> |
| struct BigEnoughPromotionImpl { |
| using type = MaxExponentPromotion<Lhs, Rhs>; |
| static constexpr bool kContained = true; |
| }; |
| |
| // We can use a twice wider type to fit. |
| template <typename Lhs, typename Rhs> |
| struct BigEnoughPromotionImpl<Lhs, Rhs, false, false> { |
| using type = |
| TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>, |
| std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>; |
| static constexpr bool kContained = true; |
| }; |
| |
| // No type is large enough. |
| template <typename Lhs, typename Rhs> |
| struct BigEnoughPromotionImpl<Lhs, Rhs, true, false> { |
| using type = MaxExponentPromotion<Lhs, Rhs>; |
| static constexpr bool kContained = false; |
| }; |
| |
| template <typename Lhs, typename Rhs> |
| using BigEnoughPromotion = BigEnoughPromotionImpl<Lhs, Rhs>::type; |
| |
| template <typename Lhs, typename Rhs> |
| inline constexpr bool kIsBigEnoughPromotionContained = |
| BigEnoughPromotionImpl<Lhs, Rhs>::kContained; |
| |
| // We can statically check if operations on the provided types can wrap, so we |
| // can skip the checked operations if they're not needed. So, for an integer we |
| // care if the destination type preserves the sign and is twice the width of |
| // the source. |
| template <typename T, typename Lhs, typename Rhs = Lhs> |
| inline constexpr bool kIsIntegerArithmeticSafe = |
| !std::is_floating_point_v<T> && !std::is_floating_point_v<Lhs> && |
| !std::is_floating_point_v<Rhs> && |
| std::is_signed_v<T> >= std::is_signed_v<Lhs> && |
| kIntegerBitsPlusSign<T> >= |
| (2 * kIntegerBitsPlusSign<Lhs>)&&std::is_signed_v<T> >= |
| std::is_signed_v<Rhs> && |
| kIntegerBitsPlusSign<T> >= (2 * kIntegerBitsPlusSign<Rhs>); |
| |
| // Promotes to a type that can represent any possible result of a binary |
| // arithmetic operation with the source types. |
| template <typename Lhs, typename Rhs> |
| struct FastIntegerArithmeticPromotionImpl { |
| using type = BigEnoughPromotion<Lhs, Rhs>; |
| static constexpr bool kContained = false; |
| }; |
| |
| template <typename Lhs, typename Rhs> |
| requires(kIsIntegerArithmeticSafe< |
| std::conditional_t<std::is_signed_v<Lhs> || std::is_signed_v<Rhs>, |
| intmax_t, |
| uintmax_t>, |
| MaxExponentPromotion<Lhs, Rhs>>) |
| struct FastIntegerArithmeticPromotionImpl<Lhs, Rhs> { |
| using type = |
| TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>, |
| std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>; |
| static_assert(kIsIntegerArithmeticSafe<type, Lhs, Rhs>); |
| static constexpr bool kContained = true; |
| }; |
| |
| template <typename Lhs, typename Rhs> |
| using FastIntegerArithmeticPromotion = |
| FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::type; |
| |
| template <typename Lhs, typename Rhs> |
| inline constexpr bool kIsFastIntegerArithmeticPromotionContained = |
| FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::kContained; |
| |
| template <typename T> |
| struct ArithmeticOrIntegralConstant { |
| using type = T; |
| }; |
| |
| template <typename T> |
| requires IntegralConstantLike<T> |
| struct ArithmeticOrIntegralConstant<T> { |
| using type = T::value_type; |
| }; |
| |
| // Extracts the underlying type from an enum. |
| template <typename T> |
| using ArithmeticOrUnderlyingEnum = |
| typename std::conditional_t<std::is_enum_v<T>, |
| std::underlying_type<T>, |
| ArithmeticOrIntegralConstant<T>>::type; |
| |
| // The following are helper templates used in the CheckedNumeric class. |
| template <typename T> |
| requires std::is_arithmetic_v<T> |
| class CheckedNumeric; |
| |
| template <typename T> |
| requires std::is_arithmetic_v<T> |
| class ClampedNumeric; |
| |
| template <typename T> |
| requires std::is_arithmetic_v<T> |
| class StrictNumeric; |
| |
| // Used to treat CheckedNumeric and arithmetic underlying types the same. |
| template <typename T> |
| inline constexpr bool kIsCheckedNumeric = false; |
| template <typename T> |
| inline constexpr bool kIsCheckedNumeric<CheckedNumeric<T>> = true; |
| template <typename T> |
| concept IsCheckedNumeric = kIsCheckedNumeric<T>; |
| |
| template <typename T> |
| inline constexpr bool kIsClampedNumeric = false; |
| template <typename T> |
| inline constexpr bool kIsClampedNumeric<ClampedNumeric<T>> = true; |
| template <typename T> |
| concept IsClampedNumeric = kIsClampedNumeric<T>; |
| |
| template <typename T> |
| inline constexpr bool kIsStrictNumeric = false; |
| template <typename T> |
| inline constexpr bool kIsStrictNumeric<StrictNumeric<T>> = true; |
| template <typename T> |
| concept IsStrictNumeric = kIsStrictNumeric<T>; |
| |
| template <typename T> |
| struct UnderlyingTypeImpl { |
| using type = ArithmeticOrUnderlyingEnum<T>; |
| }; |
| template <typename T> |
| struct UnderlyingTypeImpl<CheckedNumeric<T>> { |
| using type = T; |
| }; |
| template <typename T> |
| struct UnderlyingTypeImpl<ClampedNumeric<T>> { |
| using type = T; |
| }; |
| template <typename T> |
| struct UnderlyingTypeImpl<StrictNumeric<T>> { |
| using type = T; |
| }; |
| template <typename T> |
| using UnderlyingType = UnderlyingTypeImpl<T>::type; |
| |
| template <typename T> |
| inline constexpr bool kIsNumeric = std::is_arithmetic_v<UnderlyingType<T>>; |
| template <typename T> |
| requires(IsCheckedNumeric<T> || IsClampedNumeric<T> || IsStrictNumeric<T>) |
| inline constexpr bool kIsNumeric<T> = true; |
| template <typename T> |
| concept IsNumeric = kIsNumeric<T>; |
| |
| template <typename L, typename R> |
| concept IsCheckedOp = (IsCheckedNumeric<L> && IsNumeric<R>) || |
| (IsCheckedNumeric<R> && IsNumeric<L>); |
| |
| template <typename L, typename R> |
| concept IsClampedOp = |
| !IsCheckedOp<L, R> && ((IsClampedNumeric<L> && IsNumeric<R>) || |
| (IsClampedNumeric<R> && IsNumeric<L>)); |
| |
| template <typename L, typename R> |
| concept IsStrictOp = !IsCheckedOp<L, R> && !IsClampedOp<L, R> && |
| ((IsStrictNumeric<L> && IsNumeric<R>) || |
| (IsStrictNumeric<R> && IsNumeric<L>)); |
| |
| // as_signed<> returns the supplied integral value (or integral castable |
| // Numeric template) cast as a signed integral of equivalent precision. |
| // I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t) |
| template <typename Src, typename Dst = std::make_signed_t<UnderlyingType<Src>>> |
| requires std::integral<Dst> |
| constexpr auto as_signed(Src value) { |
| return static_cast<Dst>(value); |
| } |
| |
| // as_unsigned<> returns the supplied integral value (or integral castable |
| // Numeric template) cast as an unsigned integral of equivalent precision. |
| // I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t) |
| template <typename Src, |
| typename Dst = std::make_unsigned_t<UnderlyingType<Src>>> |
| requires std::integral<Dst> |
| constexpr auto as_unsigned(Src value) { |
| return static_cast<Dst>(value); |
| } |
| |
| template <typename L, typename R> |
| requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
| struct IsLess { |
| using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| static constexpr bool Test(L lhs, R rhs) { |
| const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| return l_range.IsUnderflow() || r_range.IsOverflow() || |
| (l_range == r_range && |
| static_cast<SumT>(lhs) < static_cast<SumT>(rhs)); |
| } |
| }; |
| |
| template <typename L, typename R> |
| requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
| struct IsLessOrEqual { |
| using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| static constexpr bool Test(L lhs, R rhs) { |
| const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| return l_range.IsUnderflow() || r_range.IsOverflow() || |
| (l_range == r_range && |
| static_cast<SumT>(lhs) <= static_cast<SumT>(rhs)); |
| } |
| }; |
| |
| template <typename L, typename R> |
| requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
| struct IsGreater { |
| using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| static constexpr bool Test(L lhs, R rhs) { |
| const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| return l_range.IsOverflow() || r_range.IsUnderflow() || |
| (l_range == r_range && |
| static_cast<SumT>(lhs) > static_cast<SumT>(rhs)); |
| } |
| }; |
| |
| template <typename L, typename R> |
| requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
| struct IsGreaterOrEqual { |
| using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| static constexpr bool Test(L lhs, R rhs) { |
| const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| return l_range.IsOverflow() || r_range.IsUnderflow() || |
| (l_range == r_range && |
| static_cast<SumT>(lhs) >= static_cast<SumT>(rhs)); |
| } |
| }; |
| |
| template <typename L, typename R> |
| requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
| struct IsEqual { |
| using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| static constexpr bool Test(L lhs, R rhs) { |
| return DstRangeRelationToSrcRange<R>(lhs) == |
| DstRangeRelationToSrcRange<L>(rhs) && |
| static_cast<SumT>(lhs) == static_cast<SumT>(rhs); |
| } |
| }; |
| |
| template <typename L, typename R> |
| requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
| struct IsNotEqual { |
| using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| static constexpr bool Test(L lhs, R rhs) { |
| return DstRangeRelationToSrcRange<R>(lhs) != |
| DstRangeRelationToSrcRange<L>(rhs) || |
| static_cast<SumT>(lhs) != static_cast<SumT>(rhs); |
| } |
| }; |
| |
| // These perform the actual math operations on the CheckedNumerics. |
| // Binary arithmetic operations. |
| template <template <typename, typename> typename C, typename L, typename R> |
| requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
| constexpr bool SafeCompare(L lhs, R rhs) { |
| using BigType = BigEnoughPromotion<L, R>; |
| return kIsBigEnoughPromotionContained<L, R> |
| // Force to a larger type for speed if both are contained. |
| ? C<BigType, BigType>::Test(static_cast<BigType>(lhs), |
| static_cast<BigType>(rhs)) |
| // Let the template functions figure it out for mixed types. |
| : C<L, R>::Test(lhs, rhs); |
| } |
| |
| template <typename Dst, typename Src> |
| inline constexpr bool kIsMaxInRangeForNumericType = |
| IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(), |
| std::numeric_limits<Src>::max()); |
| |
| template <typename Dst, typename Src> |
| inline constexpr bool kIsMinInRangeForNumericType = |
| IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(), |
| std::numeric_limits<Src>::lowest()); |
| |
| template <typename Dst, typename Src> |
| inline constexpr Dst kCommonMax = |
| kIsMaxInRangeForNumericType<Dst, Src> |
| ? static_cast<Dst>(std::numeric_limits<Src>::max()) |
| : std::numeric_limits<Dst>::max(); |
| |
| template <typename Dst, typename Src> |
| inline constexpr Dst kCommonMin = |
| kIsMinInRangeForNumericType<Dst, Src> |
| ? static_cast<Dst>(std::numeric_limits<Src>::lowest()) |
| : std::numeric_limits<Dst>::lowest(); |
| |
| // This is a wrapper to generate return the max or min for a supplied type. |
| // If the argument is false, the returned value is the maximum. If true the |
| // returned value is the minimum. |
| template <typename Dst, typename Src = Dst> |
| constexpr Dst CommonMaxOrMin(bool is_min) { |
| return is_min ? kCommonMin<Dst, Src> : kCommonMax<Dst, Src>; |
| } |
| |
| } // namespace base::internal |
| |
| #endif // BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ |