Commit 37b8843a authored by Peter Kasting's avatar Peter Kasting Committed by Chromium LUCI CQ

Checked math cleanup.

* Add const to various locally-const variables
* Remove const from function parameters
* Consistently compute result validity before setting the result, in
  preparation for making the latter conditional on the former
* Follow style guide better
* Reduce some deep indentation
* Brevity

None of these changes are intended to affect functionality.

Bug: none
Change-Id: I134e055904630f30054baafaa4b6cdb23d4eb16b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2585365
Commit-Queue: Peter Kasting <pkasting@chromium.org>
Auto-Submit: Peter Kasting <pkasting@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835899}
parent af9bb675
...@@ -21,6 +21,9 @@ class CheckedNumeric { ...@@ -21,6 +21,9 @@ class CheckedNumeric {
"CheckedNumeric<T>: T must be a numeric type."); "CheckedNumeric<T>: T must be a numeric type.");
public: public:
template <typename Src>
friend class CheckedNumeric;
using type = T; using type = T;
constexpr CheckedNumeric() = default; constexpr CheckedNumeric() = default;
...@@ -30,9 +33,6 @@ class CheckedNumeric { ...@@ -30,9 +33,6 @@ class CheckedNumeric {
constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs) constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
: state_(rhs.state_.value(), rhs.IsValid()) {} : state_(rhs.state_.value(), rhs.IsValid()) {}
template <typename Src>
friend class CheckedNumeric;
// This is not an explicit constructor because we implicitly upgrade regular // This is not an explicit constructor because we implicitly upgrade regular
// numerics to CheckedNumerics to make them easier to use. // numerics to CheckedNumerics to make them easier to use.
template <typename Src> template <typename Src>
...@@ -138,18 +138,17 @@ class CheckedNumeric { ...@@ -138,18 +138,17 @@ class CheckedNumeric {
constexpr CheckedNumeric& operator^=(const Src rhs); constexpr CheckedNumeric& operator^=(const Src rhs);
constexpr CheckedNumeric operator-() const { constexpr CheckedNumeric operator-() const {
// The negation of two's complement int min is int min, so we simply // Use an optimized code path for a known run-time variable.
// check for that in the constexpr case. if (!MustTreatAsConstexpr(state_.value()) && std::is_signed<T>::value &&
// We use an optimized code path for a known run-time variable. std::is_floating_point<T>::value) {
return MustTreatAsConstexpr(state_.value()) || !std::is_signed<T>::value || return FastRuntimeNegate();
std::is_floating_point<T>::value }
? CheckedNumeric<T>( // The negation of two's complement int min is int min.
NegateWrapper(state_.value()), const bool is_valid =
IsValid() && (!std::is_signed<T>::value || IsValid() &&
std::is_floating_point<T>::value || (!std::is_signed<T>::value || std::is_floating_point<T>::value ||
NegateWrapper(state_.value()) != NegateWrapper(state_.value()) != std::numeric_limits<T>::lowest());
std::numeric_limits<T>::lowest())) return CheckedNumeric<T>(NegateWrapper(state_.value()), is_valid);
: FastRuntimeNegate();
} }
constexpr CheckedNumeric operator~() const { constexpr CheckedNumeric operator~() const {
...@@ -199,7 +198,8 @@ class CheckedNumeric { ...@@ -199,7 +198,8 @@ class CheckedNumeric {
} }
constexpr CheckedNumeric operator--(int) { constexpr CheckedNumeric operator--(int) {
CheckedNumeric value = *this; // TODO(pkasting): Consider std::exchange() once it's constexpr in C++20.
const CheckedNumeric value = *this;
*this -= 1; *this -= 1;
return value; return value;
} }
...@@ -212,7 +212,7 @@ class CheckedNumeric { ...@@ -212,7 +212,7 @@ class CheckedNumeric {
static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) { static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) {
using Math = typename MathWrapper<M, L, R>::math; using Math = typename MathWrapper<M, L, R>::math;
T result = 0; T result = 0;
bool is_valid = const bool is_valid =
Wrapper<L>::is_valid(lhs) && Wrapper<R>::is_valid(rhs) && Wrapper<L>::is_valid(lhs) && Wrapper<R>::is_valid(rhs) &&
Math::Do(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs), &result); Math::Do(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs), &result);
return CheckedNumeric<T>(result, is_valid); return CheckedNumeric<T>(result, is_valid);
...@@ -223,8 +223,9 @@ class CheckedNumeric { ...@@ -223,8 +223,9 @@ class CheckedNumeric {
constexpr CheckedNumeric& MathOp(const R rhs) { constexpr CheckedNumeric& MathOp(const R rhs) {
using Math = typename MathWrapper<M, T, R>::math; using Math = typename MathWrapper<M, T, R>::math;
T result = 0; // Using T as the destination saves a range check. T result = 0; // Using T as the destination saves a range check.
bool is_valid = state_.is_valid() && Wrapper<R>::is_valid(rhs) && const bool is_valid =
Math::Do(state_.value(), Wrapper<R>::value(rhs), &result); state_.is_valid() && Wrapper<R>::is_valid(rhs) &&
Math::Do(state_.value(), Wrapper<R>::value(rhs), &result);
*this = CheckedNumeric<T>(result, is_valid); *this = CheckedNumeric<T>(result, is_valid);
return *this; return *this;
} }
...@@ -234,7 +235,7 @@ class CheckedNumeric { ...@@ -234,7 +235,7 @@ class CheckedNumeric {
CheckedNumeric FastRuntimeNegate() const { CheckedNumeric FastRuntimeNegate() const {
T result; T result;
bool success = CheckedSubOp<T, T>::Do(T(0), state_.value(), &result); const bool success = CheckedSubOp<T, T>::Do(T(0), state_.value(), &result);
return CheckedNumeric<T>(result, IsValid() && success); return CheckedNumeric<T>(result, IsValid() && success);
} }
...@@ -335,17 +336,17 @@ BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Min) ...@@ -335,17 +336,17 @@ BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Min)
// bad, we trigger the CHECK condition here. // bad, we trigger the CHECK condition here.
template <typename L, typename R> template <typename L, typename R>
L* operator+(L* lhs, const StrictNumeric<R> rhs) { L* operator+(L* lhs, const StrictNumeric<R> rhs) {
uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs), const uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs))) CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>(); .template ValueOrDie<uintptr_t>();
return reinterpret_cast<L*>(result); return reinterpret_cast<L*>(result);
} }
template <typename L, typename R> template <typename L, typename R>
L* operator-(L* lhs, const StrictNumeric<R> rhs) { L* operator-(L* lhs, const StrictNumeric<R> rhs) {
uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs), const uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs))) CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>(); .template ValueOrDie<uintptr_t>();
return reinterpret_cast<L*>(result); return reinterpret_cast<L*>(result);
} }
......
...@@ -27,15 +27,17 @@ constexpr bool CheckedAddImpl(T x, T y, T* result) { ...@@ -27,15 +27,17 @@ constexpr bool CheckedAddImpl(T x, T y, T* result) {
// it using the unsigned type of the same size. // it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type; using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type; using SignedDst = typename std::make_signed<T>::type;
UnsignedDst ux = static_cast<UnsignedDst>(x); const UnsignedDst ux = static_cast<UnsignedDst>(x);
UnsignedDst uy = static_cast<UnsignedDst>(y); const UnsignedDst uy = static_cast<UnsignedDst>(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy); const UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
*result = static_cast<T>(uresult);
// Addition is valid if the sign of (x + y) is equal to either that of x or // Addition is valid if the sign of (x + y) is equal to either that of x or
// that of y. // that of y.
return (std::is_signed<T>::value) const bool is_valid =
? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0 std::is_signed<T>::value
: uresult >= uy; // Unsigned is either valid or underflow. ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0
: uresult >= uy; // Unsigned is either valid or underflow.
*result = static_cast<T>(uresult);
return is_valid;
} }
template <typename T, typename U, class Enable = void> template <typename T, typename U, class Enable = void>
...@@ -75,8 +77,9 @@ struct CheckedAddOp<T, ...@@ -75,8 +77,9 @@ struct CheckedAddOp<T,
is_valid = CheckedAddImpl(static_cast<Promotion>(x), is_valid = CheckedAddImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult); static_cast<Promotion>(y), &presult);
} }
is_valid &= IsValueInRangeForNumericType<V>(presult);
*result = static_cast<V>(presult); *result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult); return is_valid;
} }
}; };
...@@ -87,15 +90,17 @@ constexpr bool CheckedSubImpl(T x, T y, T* result) { ...@@ -87,15 +90,17 @@ constexpr bool CheckedSubImpl(T x, T y, T* result) {
// it using the unsigned type of the same size. // it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type; using UnsignedDst = typename std::make_unsigned<T>::type;
using SignedDst = typename std::make_signed<T>::type; using SignedDst = typename std::make_signed<T>::type;
UnsignedDst ux = static_cast<UnsignedDst>(x); const UnsignedDst ux = static_cast<UnsignedDst>(x);
UnsignedDst uy = static_cast<UnsignedDst>(y); const UnsignedDst uy = static_cast<UnsignedDst>(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy); const UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
*result = static_cast<T>(uresult);
// Subtraction is valid if either x and y have same sign, or (x-y) and x have // Subtraction is valid if either x and y have same sign, or (x-y) and x have
// the same sign. // the same sign.
return (std::is_signed<T>::value) const bool is_valid =
? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0 std::is_signed<T>::value
: x >= y; ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0
: x >= y;
*result = static_cast<T>(uresult);
return is_valid;
} }
template <typename T, typename U, class Enable = void> template <typename T, typename U, class Enable = void>
...@@ -135,8 +140,9 @@ struct CheckedSubOp<T, ...@@ -135,8 +140,9 @@ struct CheckedSubOp<T,
is_valid = CheckedSubImpl(static_cast<Promotion>(x), is_valid = CheckedSubImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult); static_cast<Promotion>(y), &presult);
} }
is_valid &= IsValueInRangeForNumericType<V>(presult);
*result = static_cast<V>(presult); *result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult); return is_valid;
} }
}; };
...@@ -149,15 +155,17 @@ constexpr bool CheckedMulImpl(T x, T y, T* result) { ...@@ -149,15 +155,17 @@ constexpr bool CheckedMulImpl(T x, T y, T* result) {
using SignedDst = typename std::make_signed<T>::type; using SignedDst = typename std::make_signed<T>::type;
const UnsignedDst ux = SafeUnsignedAbs(x); const UnsignedDst ux = SafeUnsignedAbs(x);
const UnsignedDst uy = SafeUnsignedAbs(y); const UnsignedDst uy = SafeUnsignedAbs(y);
UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy); const UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
const bool is_negative = const bool is_negative =
std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0; std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
*result = is_negative ? 0 - uresult : uresult;
// We have a fast out for unsigned identity or zero on the second operand. // We have a fast out for unsigned identity or zero on the second operand.
// After that it's an unsigned overflow check on the absolute value, with // After that it's an unsigned overflow check on the absolute value, with
// a +1 bound for a negative result. // a +1 bound for a negative result.
return uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) || const bool is_valid =
ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy; uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) ||
ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy;
*result = is_negative ? 0 - uresult : uresult;
return is_valid;
} }
template <typename T, typename U, class Enable = void> template <typename T, typename U, class Enable = void>
...@@ -194,8 +202,9 @@ struct CheckedMulOp<T, ...@@ -194,8 +202,9 @@ struct CheckedMulOp<T,
is_valid = CheckedMulImpl(static_cast<Promotion>(x), is_valid = CheckedMulImpl(static_cast<Promotion>(x),
static_cast<Promotion>(y), &presult); static_cast<Promotion>(y), &presult);
} }
is_valid &= IsValueInRangeForNumericType<V>(presult);
*result = static_cast<V>(presult); *result = static_cast<V>(presult);
return is_valid && IsValueInRangeForNumericType<V>(presult); return is_valid;
} }
}; };
...@@ -234,9 +243,10 @@ struct CheckedDivOp<T, ...@@ -234,9 +243,10 @@ struct CheckedDivOp<T,
return false; return false;
} }
Promotion presult = Promotion(x) / Promotion(y); const Promotion presult = Promotion(x) / Promotion(y);
const bool is_valid = IsValueInRangeForNumericType<V>(presult);
*result = static_cast<V>(presult); *result = static_cast<V>(presult);
return IsValueInRangeForNumericType<V>(presult); return is_valid;
} }
}; };
...@@ -265,9 +275,11 @@ struct CheckedModOp<T, ...@@ -265,9 +275,11 @@ struct CheckedModOp<T,
return true; return true;
} }
Promotion presult = static_cast<Promotion>(x) % static_cast<Promotion>(y); const Promotion presult =
static_cast<Promotion>(x) % static_cast<Promotion>(y);
const bool is_valid = IsValueInRangeForNumericType<V>(presult);
*result = static_cast<Promotion>(presult); *result = static_cast<Promotion>(presult);
return IsValueInRangeForNumericType<V>(presult); return is_valid;
} }
}; };
...@@ -296,8 +308,11 @@ struct CheckedLshOp<T, ...@@ -296,8 +308,11 @@ struct CheckedLshOp<T,
} }
// Handle the legal corner-case of a full-width signed shift of zero. // Handle the legal corner-case of a full-width signed shift of zero.
return std::is_signed<T>::value && !x && const bool is_valid =
as_unsigned(shift) == as_unsigned(std::numeric_limits<T>::digits); std::is_signed<T>::value && !x &&
as_unsigned(shift) == as_unsigned(std::numeric_limits<T>::digits);
// TODO(pkasting): Doesn't this need to set *result = 0?
return is_valid;
} }
}; };
...@@ -315,14 +330,16 @@ struct CheckedRshOp<T, ...@@ -315,14 +330,16 @@ struct CheckedRshOp<T,
using result_type = T; using result_type = T;
template <typename V> template <typename V>
static bool Do(T x, U shift, V* result) { static bool Do(T x, U shift, V* result) {
// Use the type conversion push negative values out of range. // Use sign conversion to push negative values out of range.
if (BASE_NUMERICS_LIKELY(as_unsigned(shift) < if (BASE_NUMERICS_UNLIKELY(as_unsigned(shift) >=
IntegerBitsPlusSign<T>::value)) { IntegerBitsPlusSign<T>::value)) {
T tmp = x >> shift; return false;
*result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp);
} }
return false;
const T tmp = x >> shift;
const bool is_valid = IsValueInRangeForNumericType<V>(tmp);
*result = static_cast<V>(tmp);
return is_valid;
} }
}; };
...@@ -339,9 +356,11 @@ struct CheckedAndOp<T, ...@@ -339,9 +356,11 @@ struct CheckedAndOp<T,
typename MaxExponentPromotion<T, U>::type>::type; typename MaxExponentPromotion<T, U>::type>::type;
template <typename V> template <typename V>
static constexpr bool Do(T x, U y, V* result) { static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) & static_cast<result_type>(y); const result_type tmp =
static_cast<result_type>(x) & static_cast<result_type>(y);
const bool is_valid = IsValueInRangeForNumericType<V>(tmp);
*result = static_cast<V>(tmp); *result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp); return is_valid;
} }
}; };
...@@ -358,9 +377,11 @@ struct CheckedOrOp<T, ...@@ -358,9 +377,11 @@ struct CheckedOrOp<T,
typename MaxExponentPromotion<T, U>::type>::type; typename MaxExponentPromotion<T, U>::type>::type;
template <typename V> template <typename V>
static constexpr bool Do(T x, U y, V* result) { static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) | static_cast<result_type>(y); const result_type tmp =
static_cast<result_type>(x) | static_cast<result_type>(y);
const bool is_valid = IsValueInRangeForNumericType<V>(tmp);
*result = static_cast<V>(tmp); *result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp); return is_valid;
} }
}; };
...@@ -377,9 +398,11 @@ struct CheckedXorOp<T, ...@@ -377,9 +398,11 @@ struct CheckedXorOp<T,
typename MaxExponentPromotion<T, U>::type>::type; typename MaxExponentPromotion<T, U>::type>::type;
template <typename V> template <typename V>
static constexpr bool Do(T x, U y, V* result) { static constexpr bool Do(T x, U y, V* result) {
result_type tmp = static_cast<result_type>(x) ^ static_cast<result_type>(y); const result_type tmp =
static_cast<result_type>(x) ^ static_cast<result_type>(y);
const bool is_valid = IsValueInRangeForNumericType<V>(tmp);
*result = static_cast<V>(tmp); *result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp); return is_valid;
} }
}; };
...@@ -397,10 +420,12 @@ struct CheckedMaxOp< ...@@ -397,10 +420,12 @@ struct CheckedMaxOp<
using result_type = typename MaxExponentPromotion<T, U>::type; using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V> template <typename V>
static constexpr bool Do(T x, U y, V* result) { static constexpr bool Do(T x, U y, V* result) {
result_type tmp = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x) const result_type tmp = IsGreater<T, U>::Test(x, y)
: static_cast<result_type>(y); ? static_cast<result_type>(x)
: static_cast<result_type>(y);
const bool is_valid = IsValueInRangeForNumericType<V>(tmp);
*result = static_cast<V>(tmp); *result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp); return is_valid;
} }
}; };
...@@ -418,10 +443,12 @@ struct CheckedMinOp< ...@@ -418,10 +443,12 @@ struct CheckedMinOp<
using result_type = typename LowestValuePromotion<T, U>::type; using result_type = typename LowestValuePromotion<T, U>::type;
template <typename V> template <typename V>
static constexpr bool Do(T x, U y, V* result) { static constexpr bool Do(T x, U y, V* result) {
result_type tmp = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x) const result_type tmp = IsLess<T, U>::Test(x, y)
: static_cast<result_type>(y); ? static_cast<result_type>(x)
: static_cast<result_type>(y);
const bool is_valid = IsValueInRangeForNumericType<V>(tmp);
*result = static_cast<V>(tmp); *result = static_cast<V>(tmp);
return IsValueInRangeForNumericType<V>(tmp); return is_valid;
} }
}; };
...@@ -437,9 +464,10 @@ struct CheckedMinOp< ...@@ -437,9 +464,10 @@ struct CheckedMinOp<
template <typename V> \ template <typename V> \
static constexpr bool Do(T x, U y, V* result) { \ static constexpr bool Do(T x, U y, V* result) { \
using Promotion = typename MaxExponentPromotion<T, U>::type; \ using Promotion = typename MaxExponentPromotion<T, U>::type; \
Promotion presult = x OP y; \ const Promotion presult = x OP y; \
const bool is_valid = IsValueInRangeForNumericType<V>(presult); \
*result = static_cast<V>(presult); \ *result = static_cast<V>(presult); \
return IsValueInRangeForNumericType<V>(presult); \ return is_valid; \
} \ } \
}; };
...@@ -475,91 +503,52 @@ class CheckedNumericState {}; ...@@ -475,91 +503,52 @@ class CheckedNumericState {};
// Integrals require quite a bit of additional housekeeping to manage state. // Integrals require quite a bit of additional housekeeping to manage state.
template <typename T> template <typename T>
class CheckedNumericState<T, NUMERIC_INTEGER> { class CheckedNumericState<T, NUMERIC_INTEGER> {
private:
// is_valid_ precedes value_ because member intializers in the constructors
// are evaluated in field order, and is_valid_ must be read when initializing
// value_.
bool is_valid_;
T value_;
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrZero(const Src value,
const bool is_valid) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return (std::is_integral<SrcType>::value || is_valid)
? static_cast<T>(value)
: static_cast<T>(0);
}
public: public:
template <typename Src, NumericRepresentation type> template <typename Src = int>
friend class CheckedNumericState; constexpr explicit CheckedNumericState(Src value = 0, bool is_valid = true)
constexpr CheckedNumericState() : is_valid_(true), value_(0) {}
template <typename Src>
constexpr CheckedNumericState(Src value, bool is_valid)
: is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)), : is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
value_(WellDefinedConversionOrZero(value, is_valid_)) { value_(WellDefinedConversionOrZero(value, is_valid_)) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric."); static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
} }
// Copy constructor.
template <typename Src> template <typename Src>
constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs) constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
: is_valid_(rhs.IsValid()), : CheckedNumericState(rhs.value(), rhs.is_valid()) {}
value_(WellDefinedConversionOrZero(rhs.value(), is_valid_)) {}
template <typename Src>
constexpr explicit CheckedNumericState(Src value)
: is_valid_(IsValueInRangeForNumericType<T>(value)),
value_(WellDefinedConversionOrZero(value, is_valid_)) {}
constexpr bool is_valid() const { return is_valid_; } constexpr bool is_valid() const { return is_valid_; }
constexpr T value() const { return value_; } constexpr T value() const { return value_; }
};
// Floating points maintain their own validity, but need translation wrappers.
template <typename T>
class CheckedNumericState<T, NUMERIC_FLOATING> {
private: private:
T value_;
// Ensures that a type conversion does not trigger undefined behavior. // Ensures that a type conversion does not trigger undefined behavior.
template <typename Src> template <typename Src>
static constexpr T WellDefinedConversionOrNaN(const Src value, static constexpr T WellDefinedConversionOrZero(Src value, bool is_valid) {
const bool is_valid) {
using SrcType = typename internal::UnderlyingType<Src>::type; using SrcType = typename internal::UnderlyingType<Src>::type;
return (StaticDstRangeRelationToSrcRange<T, SrcType>::value == return (std::is_integral<SrcType>::value || is_valid)
NUMERIC_RANGE_CONTAINED ||
is_valid)
? static_cast<T>(value) ? static_cast<T>(value)
: std::numeric_limits<T>::quiet_NaN(); : 0;
} }
public: // is_valid_ precedes value_ because member intializers in the constructors
template <typename Src, NumericRepresentation type> // are evaluated in field order, and is_valid_ must be read when initializing
friend class CheckedNumericState; // value_.
bool is_valid_;
constexpr CheckedNumericState() : value_(0.0) {} T value_;
};
template <typename Src>
constexpr CheckedNumericState(Src value, bool is_valid)
: value_(WellDefinedConversionOrNaN(value, is_valid)) {}
template <typename Src> // Floating points maintain their own validity, but need translation wrappers.
constexpr explicit CheckedNumericState(Src value) template <typename T>
class CheckedNumericState<T, NUMERIC_FLOATING> {
public:
template <typename Src = double>
constexpr explicit CheckedNumericState(Src value = 0.0, bool is_valid = true)
: value_(WellDefinedConversionOrNaN( : value_(WellDefinedConversionOrNaN(
value, value,
IsValueInRangeForNumericType<T>(value))) {} is_valid && IsValueInRangeForNumericType<T>(value))) {}
// Copy constructor.
template <typename Src> template <typename Src>
constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs) constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
: value_(WellDefinedConversionOrNaN( : CheckedNumericState(rhs.value(), rhs.is_valid()) {}
rhs.value(),
rhs.is_valid() && IsValueInRangeForNumericType<T>(rhs.value()))) {}
constexpr bool is_valid() const { constexpr bool is_valid() const {
// Written this way because std::isfinite is not reliably constexpr. // Written this way because std::isfinite is not reliably constexpr.
...@@ -568,7 +557,22 @@ class CheckedNumericState<T, NUMERIC_FLOATING> { ...@@ -568,7 +557,22 @@ class CheckedNumericState<T, NUMERIC_FLOATING> {
value_ >= std::numeric_limits<T>::lowest() value_ >= std::numeric_limits<T>::lowest()
: std::isfinite(value_); : std::isfinite(value_);
} }
constexpr T value() const { return value_; } constexpr T value() const { return value_; }
private:
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrNaN(Src value, bool is_valid) {
using SrcType = typename internal::UnderlyingType<Src>::type;
return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
NUMERIC_RANGE_CONTAINED ||
is_valid)
? static_cast<T>(value)
: std::numeric_limits<T>::quiet_NaN();
}
T value_;
}; };
} // namespace internal } // namespace internal
......
...@@ -36,8 +36,9 @@ struct CheckedMulFastAsmOp { ...@@ -36,8 +36,9 @@ struct CheckedMulFastAsmOp {
Promotion presult; Promotion presult;
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y); presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
const bool is_valid = IsValueInRangeForNumericType<V>(presult);
*result = static_cast<V>(presult); *result = static_cast<V>(presult);
return IsValueInRangeForNumericType<V>(presult); return is_valid;
} }
}; };
...@@ -104,9 +105,9 @@ struct ClampedMulFastAsmOp { ...@@ -104,9 +105,9 @@ struct ClampedMulFastAsmOp {
if (!IsIntegerArithmeticSafe<int32_t, T, U>::value && if (!IsIntegerArithmeticSafe<int32_t, T, U>::value &&
!IsIntegerArithmeticSafe<uint32_t, T, U>::value) { !IsIntegerArithmeticSafe<uint32_t, T, U>::value) {
V result; V result;
if (CheckedMulFastAsmOp<T, U>::Do(x, y, &result)) return CheckedMulFastAsmOp<T, U>::Do(x, y, &result)
return result; ? result
return CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y)); : CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
} }
assert((FastIntegerArithmeticPromotion<T, U>::is_contained)); assert((FastIntegerArithmeticPromotion<T, U>::is_contained));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment