Commit 186c7dbb authored by jschuh's avatar jschuh Committed by Commit bot

Support saturation overrides in saturated_cast

This is a requirement for adding a full saturated type implementation.

BUG=672489
NOTRY=true

Review-Url: https://codereview.chromium.org/2578613002
Cr-Commit-Position: refs/heads/master@{#439061}
parent cde936a4
...@@ -55,22 +55,6 @@ constexpr bool IsValueInRangeForNumericType(Src value) { ...@@ -55,22 +55,6 @@ constexpr bool IsValueInRangeForNumericType(Src value) {
internal::RANGE_VALID; internal::RANGE_VALID;
} }
// Convenience function for determining if a numeric value is negative without
// throwing compiler warnings on: unsigned(value) < 0.
template <typename T,
typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T value) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return value < 0;
}
template <typename T,
typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return false;
}
// Forces a crash, like a CHECK(false). Used for numeric boundary errors. // Forces a crash, like a CHECK(false). Used for numeric boundary errors.
struct CheckOnFailure { struct CheckOnFailure {
template <typename T> template <typename T>
...@@ -99,62 +83,62 @@ constexpr Dst checked_cast(Src value) { ...@@ -99,62 +83,62 @@ constexpr Dst checked_cast(Src value) {
: CheckHandler::template HandleFailure<Dst>(); : CheckHandler::template HandleFailure<Dst>();
} }
// HandleNaN will return 0 in this case. // Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
struct SaturatedCastNaNBehaviorReturnZero { template <typename T>
template <typename T> struct SaturatedCastDefaultHandler {
static constexpr T HandleFailure() { static constexpr T HandleNaN() {
return T(); return std::numeric_limits<T>::has_quiet_NaN
? std::numeric_limits<T>::quiet_NaN()
: T();
}
static constexpr T max() { return std::numeric_limits<T>::max(); }
static constexpr T HandleOverflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity()
: std::numeric_limits<T>::max();
}
static constexpr T lowest() { return std::numeric_limits<T>::lowest(); }
static constexpr T HandleUnderflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity() * -1
: std::numeric_limits<T>::lowest();
} }
}; };
namespace internal { namespace internal {
// These wrappers are used for C++11 constexpr support by avoiding both the
// declaration of local variables and invalid evaluation resulting from the
// lack of "constexpr if" support in the saturated_cast template function.
// TODO(jschuh): Convert to single function with a switch once we support C++14.
template <
typename Dst,
class NaNHandler,
typename Src,
typename std::enable_if<std::is_integral<Dst>::value>::type* = nullptr>
constexpr Dst saturated_cast_impl(const Src value,
const RangeConstraint constraint) {
return constraint == RANGE_VALID
? static_cast<Dst>(value)
: (constraint == RANGE_UNDERFLOW
? std::numeric_limits<Dst>::lowest()
: (constraint == RANGE_OVERFLOW
? std::numeric_limits<Dst>::max()
: NaNHandler::template HandleFailure<Dst>()));
}
template <typename Dst,
class NaNHandler,
typename Src,
typename std::enable_if<std::is_floating_point<Dst>::value>::type* =
nullptr>
constexpr Dst saturated_cast_impl(const Src value,
const RangeConstraint constraint) {
return constraint == RANGE_VALID
? static_cast<Dst>(value)
: (constraint == RANGE_UNDERFLOW
? -std::numeric_limits<Dst>::infinity()
: (constraint == RANGE_OVERFLOW
? std::numeric_limits<Dst>::infinity()
: std::numeric_limits<Dst>::quiet_NaN()));
}
// saturated_cast<> is analogous to static_cast<> for numeric types, except // saturated_cast<> is analogous to static_cast<> for numeric types, except
// that the specified numeric conversion will saturate rather than overflow or // that the specified numeric conversion will saturate by default rather than
// underflow. NaN assignment to an integral will defer the behavior to a // overflow or underflow, and NaN assignment to an integral will return 0.
// specified class. By default, it will return 0. // All boundary condition behaviors can be overriden with a custom handler.
template <typename Dst, template <typename Dst,
class NaNHandler = SaturatedCastNaNBehaviorReturnZero, template <typename>
class SaturationHandler = SaturatedCastDefaultHandler,
typename Src> typename Src>
constexpr Dst saturated_cast(Src value) { constexpr Dst saturated_cast(Src value) {
static_assert(
SaturationHandler<Dst>::lowest() < SaturationHandler<Dst>::max(), "");
// While this looks like a lot of code, it's all constexpr and all but
// one variable are compile-time constants (enforced by a static_assert).
// So, it should evaluate to the minimum number of comparisons required
// for the range check, which is 0-3, depending on the exact source and
// destination types, and whatever custom range is specified.
using SrcType = typename UnderlyingType<Src>::type; using SrcType = typename UnderlyingType<Src>::type;
return internal::saturated_cast_impl<Dst, NaNHandler>( return IsGreaterOrEqual<SrcType, Dst>::Test(
value, internal::DstRangeRelationToSrcRange<Dst, SrcType>(value)); value, NarrowingRange<Dst, SrcType, SaturationHandler>::lowest())
? (IsLessOrEqual<SrcType, Dst>::Test(
value,
NarrowingRange<Dst, SrcType, SaturationHandler>::max())
? static_cast<Dst>(value)
: SaturationHandler<Dst>::HandleOverflow())
// This last branch is a little confusing. It's specifically to
// catch NaN when converting from float to integral.
: (std::is_integral<SrcType>::value ||
std::is_floating_point<Dst>::value ||
IsLessOrEqual<SrcType, Dst>::Test(
value, NarrowingRange<Dst, SrcType,
SaturationHandler>::max())
? SaturationHandler<Dst>::HandleUnderflow()
: SaturationHandler<Dst>::HandleNaN());
} }
// strict_cast<> is analogous to static_cast<> for numeric types, except that // strict_cast<> is analogous to static_cast<> for numeric types, except that
...@@ -285,6 +269,7 @@ using internal::saturated_cast; ...@@ -285,6 +269,7 @@ using internal::saturated_cast;
using internal::SafeUnsignedAbs; using internal::SafeUnsignedAbs;
using internal::StrictNumeric; using internal::StrictNumeric;
using internal::MakeStrictNum; using internal::MakeStrictNum;
using internal::IsValueNegative;
// Explicitly make a shorter size_t alias for convenience. // Explicitly make a shorter size_t alias for convenience.
using SizeT = StrictNumeric<size_t>; using SizeT = StrictNumeric<size_t>;
......
...@@ -50,6 +50,36 @@ constexpr T BinaryComplement(T x) { ...@@ -50,6 +50,36 @@ constexpr T BinaryComplement(T x) {
return static_cast<T>(~x); return static_cast<T>(~x);
} }
// Determines if a numeric value is negative without throwing compiler
// warnings on: unsigned(value) < 0.
template <typename T,
typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T value) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return value < 0;
}
template <typename T,
typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
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>
constexpr typename std::make_signed<T>::type ConditionalNegate(
T x,
bool is_negative) {
static_assert(std::is_integral<T>::value, "Type must be integral");
using SignedT = typename std::make_signed<T>::type;
using UnsignedT = typename std::make_unsigned<T>::type;
return static_cast<SignedT>(
(static_cast<UnsignedT>(x) ^ -SignedT(is_negative)) + is_negative);
}
// This performs a safe, non-branching absolute value via unsigned overflow. // This performs a safe, non-branching absolute value via unsigned overflow.
template <typename T> template <typename T>
constexpr T SafeUnsignedAbsImpl(T value, T sign_mask) { constexpr T SafeUnsignedAbsImpl(T value, T sign_mask) {
...@@ -181,28 +211,44 @@ constexpr inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound, ...@@ -181,28 +211,44 @@ constexpr inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound,
// To fix this bug we manually truncate the maximum value when the destination // 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, // 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. // such that the resulting maximum is represented exactly as a floating point.
template <typename Dst, typename Src> template <typename Dst,
typename Src,
template <typename> class Bounds = std::numeric_limits>
struct NarrowingRange { struct NarrowingRange {
using SrcLimits = typename std::numeric_limits<Src>; using SrcLimits = typename std::numeric_limits<Src>;
using DstLimits = typename std::numeric_limits<Dst>; using DstLimits = typename std::numeric_limits<Dst>;
// The following logic avoids warnings where the max function is
// instantiated with invalid values for a bit shift (even though // Computes the mask required to make an accurate comparison between types.
// such a function can never be called). static const int kShift =
static const int shift = (MaxExponent<Src>::value > MaxExponent<Dst>::value && (MaxExponent<Src>::value > MaxExponent<Dst>::value &&
SrcLimits::digits < DstLimits::digits && SrcLimits::digits < DstLimits::digits)
SrcLimits::is_iec559 && ? (DstLimits::digits - SrcLimits::digits)
DstLimits::is_integer) : 0;
? (DstLimits::digits - SrcLimits::digits) template <
: 0; typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
static constexpr Dst max() {
// We use UINTMAX_C below to avoid compiler warnings about shifting floating // Masks out the integer bits that are beyond the precision of the
// points. Since it's a compile time calculation, it shouldn't have any // intermediate type used for comparison.
// performance impact. static constexpr T Adjust(T value) {
return DstLimits::max() - static_cast<Dst>((UINTMAX_C(1) << shift) - 1); static_assert(std::is_same<T, Dst>::value, "");
static_assert(kShift < DstLimits::digits, "");
return static_cast<T>(
ConditionalNegate(SafeUnsignedAbs(value) & ~((T(1) << kShift) - T(1)),
IsValueNegative(value)));
}
template <typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* =
nullptr>
static constexpr T Adjust(T value) {
static_assert(std::is_same<T, Dst>::value, "");
static_assert(kShift == 0, "");
return value;
} }
static constexpr Dst lowest() { return DstLimits::lowest(); } static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); }
static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); }
}; };
template <typename Dst, template <typename Dst,
......
...@@ -831,7 +831,25 @@ static_assert(std::is_same<decltype(TestOverload(StrictNumeric<size_t>())), ...@@ -831,7 +831,25 @@ static_assert(std::is_same<decltype(TestOverload(StrictNumeric<size_t>())),
size_t>::value, size_t>::value,
""); "");
TEST(SafeNumerics, CastTests) { template <typename T>
struct CastTest1 {
static constexpr T HandleNaN() { return -1; }
static constexpr T max() { return numeric_limits<T>::max() - 1; }
static constexpr T HandleOverflow() { return max(); }
static constexpr T lowest() { return numeric_limits<T>::lowest() + 1; }
static constexpr T HandleUnderflow() { return lowest(); }
};
template <typename T>
struct CastTest2 {
static constexpr T HandleNaN() { return 11; }
static constexpr T max() { return 10; }
static constexpr T HandleOverflow() { return max(); }
static constexpr T lowest() { return 1; }
static constexpr T HandleUnderflow() { return lowest(); }
};
TEST(SafeNumerics, ) {
// MSVC catches and warns that we're forcing saturation in these tests. // MSVC catches and warns that we're forcing saturation in these tests.
// Since that's intentional, we need to shut this warning off. // Since that's intentional, we need to shut this warning off.
#if defined(COMPILER_MSVC) #if defined(COMPILER_MSVC)
...@@ -893,6 +911,35 @@ TEST(SafeNumerics, CastTests) { ...@@ -893,6 +911,35 @@ TEST(SafeNumerics, CastTests) {
saturated_cast<int>(double_small_int)); saturated_cast<int>(double_small_int));
EXPECT_EQ(numeric_limits<int>::max(), saturated_cast<int>(double_large_int)); EXPECT_EQ(numeric_limits<int>::max(), saturated_cast<int>(double_large_int));
// Test the saturated cast overrides.
using FloatLimits = numeric_limits<float>;
using IntLimits = numeric_limits<int>;
EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(FloatLimits::quiet_NaN())));
EXPECT_EQ(CastTest1<int>::max(),
(saturated_cast<int, CastTest1>(FloatLimits::infinity())));
EXPECT_EQ(CastTest1<int>::max(),
(saturated_cast<int, CastTest1>(FloatLimits::max())));
EXPECT_EQ(CastTest1<int>::max(),
(saturated_cast<int, CastTest1>(float(IntLimits::max()))));
EXPECT_EQ(CastTest1<int>::lowest(),
(saturated_cast<int, CastTest1>(-FloatLimits::infinity())));
EXPECT_EQ(CastTest1<int>::lowest(),
(saturated_cast<int, CastTest1>(FloatLimits::lowest())));
EXPECT_EQ(0, (saturated_cast<int, CastTest1>(0.0)));
EXPECT_EQ(1, (saturated_cast<int, CastTest1>(1.0)));
EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(-1.0)));
EXPECT_EQ(0, (saturated_cast<int, CastTest1>(0)));
EXPECT_EQ(1, (saturated_cast<int, CastTest1>(1)));
EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(-1)));
EXPECT_EQ(CastTest1<int>::lowest(),
(saturated_cast<int, CastTest1>(float(IntLimits::lowest()))));
EXPECT_EQ(11, (saturated_cast<int, CastTest2>(FloatLimits::quiet_NaN())));
EXPECT_EQ(10, (saturated_cast<int, CastTest2>(FloatLimits::infinity())));
EXPECT_EQ(10, (saturated_cast<int, CastTest2>(FloatLimits::max())));
EXPECT_EQ(1, (saturated_cast<int, CastTest2>(-FloatLimits::infinity())));
EXPECT_EQ(1, (saturated_cast<int, CastTest2>(FloatLimits::lowest())));
EXPECT_EQ(1, (saturated_cast<int, CastTest2>(0U)));
float not_a_number = std::numeric_limits<float>::infinity() - float not_a_number = std::numeric_limits<float>::infinity() -
std::numeric_limits<float>::infinity(); std::numeric_limits<float>::infinity();
EXPECT_TRUE(std::isnan(not_a_number)); EXPECT_TRUE(std::isnan(not_a_number));
...@@ -937,15 +984,6 @@ TEST(SafeNumerics, CastTests) { ...@@ -937,15 +984,6 @@ TEST(SafeNumerics, CastTests) {
EXPECT_EQ(1, strict_cast<int>(StrictNumeric<int>(1))); EXPECT_EQ(1, strict_cast<int>(StrictNumeric<int>(1)));
} }
TEST(SafeNumerics, SaturatedCastChecks) {
float not_a_number = std::numeric_limits<float>::infinity() -
std::numeric_limits<float>::infinity();
EXPECT_TRUE(std::isnan(not_a_number));
EXPECT_DEATH_IF_SUPPORTED(
(saturated_cast<int, base::CheckOnFailure>(not_a_number)),
"");
}
TEST(SafeNumerics, IsValueInRangeForNumericType) { TEST(SafeNumerics, IsValueInRangeForNumericType) {
EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(0)); EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(0));
EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(1)); EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(1));
......
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