blob: 4e42d456e4b33738675bb069eab7f0e65e6ba4d8 [file] [log] [blame]
// 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_