#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_H_
#include <stddef.h>
#include <cmath>
#include <concepts>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions_impl.h"
#if defined(__ARMEL__)
#include "base/numerics/safe_conversions_arm_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
#endif
namespace base {
namespace numerics_internal {
#if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static constexpr bool is_supported = false;
static constexpr Dst Do(Src) {
return CheckOnFailure::template HandleFailure<Dst>();
}
};
#endif
#undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
template <typename Dst, typename Src>
struct IsValueInRangeFastOp {
static constexpr bool is_supported = false;
static constexpr bool Do(Src) {
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename Dst, typename Src>
requires(std::signed_integral<Dst> && std::signed_integral<Src> &&
!kIsTypeInRangeForNumericType<Dst, Src>)
struct IsValueInRangeFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr bool Do(Src value) {
return value == static_cast<Dst>(value);
}
};
template <typename Dst, typename Src>
requires(std::unsigned_integral<Dst> && std::signed_integral<Src> &&
!kIsTypeInRangeForNumericType<Dst, Src>)
struct IsValueInRangeFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr bool Do(Src value) {
return as_unsigned(value) <= as_unsigned(kCommonMax<Src, Dst>);
}
};
template <typename Dst, typename Src>
requires(IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
std::numeric_limits<Dst>::lowest() < std::numeric_limits<Dst>::max())
constexpr bool IsValueInRangeForNumericType(Src value) {
using SrcType = UnderlyingType<Src>;
const auto underlying_value = static_cast<SrcType>(value);
return numerics_internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
? numerics_internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
underlying_value)
: numerics_internal::DstRangeRelationToSrcRange<Dst>(
underlying_value)
.IsValid();
}
template <typename Dst,
class CheckHandler = numerics_internal::CheckOnFailure,
typename Src>
requires(IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
std::numeric_limits<Dst>::lowest() < std::numeric_limits<Dst>::max())
constexpr Dst checked_cast(Src value) {
if (IsValueInRangeForNumericType<Dst>(value)) [[likely]] {
return static_cast<Dst>(static_cast<UnderlyingType<Src>>(value));
}
return CheckHandler::template HandleFailure<Dst>();
}
template <typename T>
struct SaturationDefaultLimits : public std::numeric_limits<T> {
static constexpr T NaN() {
if constexpr (std::numeric_limits<T>::has_quiet_NaN) {
return std::numeric_limits<T>::quiet_NaN();
} else {
return T();
}
}
using std::numeric_limits<T>::max;
static constexpr T Overflow() {
if constexpr (std::numeric_limits<T>::has_infinity) {
return std::numeric_limits<T>::infinity();
} else {
return std::numeric_limits<T>::max();
}
}
using std::numeric_limits<T>::lowest;
static constexpr T Underflow() {
if constexpr (std::numeric_limits<T>::has_infinity) {
return std::numeric_limits<T>::infinity() * -1;
} else {
return std::numeric_limits<T>::lowest();
}
}
};
template <typename Dst, template <typename> class S, typename Src>
constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
return !constraint.IsOverflowFlagSet()
? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value)
: S<Dst>::Underflow())
: (std::is_integral_v<Src> || !constraint.IsUnderflowFlagSet()
? S<Dst>::Overflow()
: S<Dst>::NaN());
}
template <typename Dst, typename Src>
struct SaturateFastOp {
static constexpr bool is_supported = false;
static constexpr Dst Do(Src) {
return CheckOnFailure::template HandleFailure<Dst>();
}
};
template <typename Dst, typename Src>
requires(std::integral<Src> && std::integral<Dst> &&
SaturateFastAsmOp<Dst, Src>::is_supported)
struct SaturateFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr Dst Do(Src value) {
return SaturateFastAsmOp<Dst, Src>::Do(value);
}
};
template <typename Dst, typename Src>
requires(std::integral<Src> && std::integral<Dst> &&
!SaturateFastAsmOp<Dst, Src>::is_supported)
struct SaturateFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr Dst Do(Src value) {
const Dst saturated = CommonMaxOrMin<Dst, Src>(
kIsMaxInRangeForNumericType<Dst, Src> ||
(!kIsMinInRangeForNumericType<Dst, Src> && IsValueNegative(value)));
if (IsValueInRangeForNumericType<Dst>(value)) [[likely]] {
return static_cast<Dst>(value);
}
return saturated;
}
};
template <typename Dst,
template <typename> class SaturationHandler = SaturationDefaultLimits,
typename Src>
constexpr Dst saturated_cast(Src value) {
using SrcType = UnderlyingType<Src>;
const auto underlying_value = static_cast<SrcType>(value);
return !std::is_constant_evaluated() &&
SaturateFastOp<Dst, SrcType>::is_supported &&
std::is_same_v<SaturationHandler<Dst>,
SaturationDefaultLimits<Dst>>
? SaturateFastOp<Dst, SrcType>::Do(underlying_value)
: saturated_cast_impl<Dst, SaturationHandler, SrcType>(
underlying_value,
DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
underlying_value));
}
template <typename Dst, typename Src, typename SrcType = UnderlyingType<Src>>
requires(
IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
kStaticDstRangeRelationToSrcRange<Dst, SrcType> ==
NumericRangeRepresentation::kContained)
constexpr Dst strict_cast(Src value) {
return static_cast<Dst>(static_cast<SrcType>(value));
}
template <typename Dst, typename Src>
inline constexpr bool kIsNumericRangeContained = false;
template <typename Dst, typename Src>
requires(std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Dst>> &&
std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Src>>)
inline constexpr bool kIsNumericRangeContained<Dst, Src> =
kStaticDstRangeRelationToSrcRange<Dst, Src> ==
NumericRangeRepresentation::kContained;
template <typename T>
requires std::is_arithmetic_v<T>
class StrictNumeric {
public:
using type = T;
constexpr StrictNumeric() : value_(0) {}
template <typename Src>
constexpr StrictNumeric(const StrictNumeric<Src>& rhs)
: value_(strict_cast<T>(rhs.value_)) {}
template <typename Src>
constexpr StrictNumeric(Src value) : value_(strict_cast<T>(value)) {}
template <typename Dst>
requires(kIsNumericRangeContained<Dst, T>)
constexpr operator Dst() const {
return static_cast<ArithmeticOrUnderlyingEnum<Dst>>(value_);
}
constexpr bool operator!() const { return !value_; }
private:
template <typename U>
requires std::is_arithmetic_v<U>
friend class StrictNumeric;
T value_;
};
template <typename T>
StrictNumeric(T) -> StrictNumeric<T>;
template <typename T>
constexpr StrictNumeric<UnderlyingType<T>> MakeStrictNum(const T value) {
return value;
}
#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP) \
template <typename L, typename R> \
requires(numerics_internal::Is##CLASS##Op<L, R>) \
constexpr bool operator OP(L lhs, R rhs) { \
return SafeCompare<NAME, UnderlyingType<L>, UnderlyingType<R>>(lhs, rhs); \
}
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLessOrEqual, <=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreater, >)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreaterOrEqual, >=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsEqual, ==)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=)
}
using numerics_internal::as_signed;
using numerics_internal::as_unsigned;
using numerics_internal::checked_cast;
using numerics_internal::IsValueInRangeForNumericType;
using numerics_internal::IsValueNegative;
using numerics_internal::kIsTypeInRangeForNumericType;
using numerics_internal::MakeStrictNum;
using numerics_internal::SafeUnsignedAbs;
using numerics_internal::saturated_cast;
using numerics_internal::strict_cast;
using numerics_internal::StrictNumeric;
using SizeT = StrictNumeric<size_t>;
template <typename Dst = int, typename Src>
requires(std::integral<Dst> && std::floating_point<Src>)
Dst ClampFloor(Src value) {
return saturated_cast<Dst>(std::floor(value));
}
template <typename Dst = int, typename Src>
requires(std::integral<Dst> && std::floating_point<Src>)
Dst ClampCeil(Src value) {
return saturated_cast<Dst>(std::ceil(value));
}
template <typename Dst = int, typename Src>
requires(std::integral<Dst> && std::floating_point<Src>)
Dst ClampRound(Src value) {
const Src rounded = std::round(value);
return saturated_cast<Dst>(rounded);
}
}
#endif