Commit 8b34cfe3 authored by jschuh's avatar jschuh Committed by Commit bot

Improve saturated_cast performance

Plumb custom bounds through DstRangeRelationToSrcRange to remove
duplicate comparisons. Refactor RangeConstraint from an enum to a class
containing bools, which can be independently evaluated at compile-time.

NOTRY=true

Review-Url: https://codereview.chromium.org/2585043002
Cr-Commit-Position: refs/heads/master@{#439508}
parent ef60bcc9
...@@ -106,6 +106,26 @@ struct SaturatedCastDefaultHandler { ...@@ -106,6 +106,26 @@ struct SaturatedCastDefaultHandler {
}; };
namespace internal { namespace internal {
// saturated_cast<> is analogous to static_cast<> for numeric types, except
// that the specified numeric conversion will saturate by default rather than
// overflow or underflow, and NaN assignment to an integral will return 0.
// All boundary condition behaviors can be overriden with a custom handler.
template <template <typename>
class SaturationHandler = SaturatedCastDefaultHandler,
typename Dst,
typename Src>
constexpr Dst saturated_cast_impl(const Src value,
const RangeConstraint constraint) {
return constraint.IsValid()
? static_cast<Dst>(value)
: (constraint.IsOverflow()
? SaturationHandler<Dst>::HandleOverflow()
// Skip this check for integral Src, which cannot be NaN.
: (std::is_integral<Src>::value || constraint.IsUnderflow()
? SaturationHandler<Dst>::HandleUnderflow()
: SaturationHandler<Dst>::HandleNaN()));
}
// 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 by default rather than // that the specified numeric conversion will saturate by default rather than
// overflow or underflow, and NaN assignment to an integral will return 0. // overflow or underflow, and NaN assignment to an integral will return 0.
...@@ -115,30 +135,10 @@ template <typename Dst, ...@@ -115,30 +135,10 @@ template <typename Dst,
class SaturationHandler = SaturatedCastDefaultHandler, 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 IsGreaterOrEqual<SrcType, Dst>::Test( return saturated_cast_impl<SaturationHandler, Dst>(
value, NarrowingRange<Dst, SrcType, SaturationHandler>::lowest()) static_cast<SrcType>(value),
? (IsLessOrEqual<SrcType, Dst>::Test( DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(value));
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
......
...@@ -166,29 +166,38 @@ struct StaticDstRangeRelationToSrcRange<Dst, ...@@ -166,29 +166,38 @@ struct StaticDstRangeRelationToSrcRange<Dst,
static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED; static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
}; };
enum RangeConstraint { enum RangeConstraintEnum {
RANGE_VALID = 0x0, // Value can be represented by the destination type. RANGE_VALID = 0x0, // Value can be represented by the destination type.
RANGE_UNDERFLOW = 0x1, // Value would overflow. RANGE_OVERFLOW = 0x1, // Value would overflow.
RANGE_OVERFLOW = 0x2, // Value would underflow. RANGE_UNDERFLOW = 0x2, // Value would underflow.
RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN). RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN).
}; };
// Helper function for coercing an int back to a RangeContraint. // This class wraps the range constraints as separate booleans so the compiler
constexpr RangeConstraint GetRangeConstraint(int integer_range_constraint) { // can identify constants and eliminate unused code paths.
// TODO(jschuh): Once we get full C++14 support we want this class RangeConstraint {
// assert(integer_range_constraint >= RANGE_VALID && public:
// integer_range_constraint <= RANGE_INVALID) constexpr RangeConstraint(bool is_in_upper_bound, bool is_in_lower_bound)
return static_cast<RangeConstraint>(integer_range_constraint); : is_overflow_(!is_in_upper_bound), is_underflow_(!is_in_lower_bound) {}
} constexpr RangeConstraint() : is_overflow_(0), is_underflow_(0) {}
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_; }
// These are some wrappers to make the tests a bit cleaner.
constexpr operator RangeConstraintEnum() const {
return static_cast<RangeConstraintEnum>(
static_cast<int>(is_overflow_) | static_cast<int>(is_underflow_) << 1);
}
constexpr bool operator==(const RangeConstraintEnum rhs) const {
return rhs == static_cast<RangeConstraintEnum>(*this);
}
// This function creates a RangeConstraint from an upper and lower bound private:
// check by taking advantage of the fact that only NaN can be out of range in const bool is_overflow_;
// both directions at once. const bool is_underflow_;
constexpr inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound, };
bool is_in_lower_bound) {
return GetRangeConstraint((is_in_upper_bound ? 0 : RANGE_OVERFLOW) |
(is_in_lower_bound ? 0 : RANGE_UNDERFLOW));
}
// The following helper template addresses a corner case in range checks for // 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 // conversion from a floating-point type to an integral type of smaller range
...@@ -211,11 +220,9 @@ constexpr inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound, ...@@ -211,11 +220,9 @@ 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, template <typename Dst, typename Src, template <typename> class Bounds>
typename Src,
template <typename> class Bounds = std::numeric_limits>
struct NarrowingRange { struct NarrowingRange {
using SrcLimits = typename std::numeric_limits<Src>; using SrcLimits = std::numeric_limits<Src>;
using DstLimits = typename std::numeric_limits<Dst>; using DstLimits = typename std::numeric_limits<Dst>;
// Computes the mask required to make an accurate comparison between types. // Computes the mask required to make an accurate comparison between types.
...@@ -253,6 +260,7 @@ struct NarrowingRange { ...@@ -253,6 +260,7 @@ struct NarrowingRange {
template <typename Dst, template <typename Dst,
typename Src, typename Src,
template <typename> class Bounds,
IntegerRepresentation DstSign = std::is_signed<Dst>::value IntegerRepresentation DstSign = std::is_signed<Dst>::value
? INTEGER_REPRESENTATION_SIGNED ? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED, : INTEGER_REPRESENTATION_UNSIGNED,
...@@ -267,83 +275,112 @@ struct DstRangeRelationToSrcRangeImpl; ...@@ -267,83 +275,112 @@ struct DstRangeRelationToSrcRangeImpl;
// split it into checks based on signedness to avoid confusing casts and // split it into checks based on signedness to avoid confusing casts and
// compiler warnings on signed an unsigned comparisons. // compiler warnings on signed an unsigned comparisons.
// Dst range is statically determined to contain Src: Nothing to check. // Same sign narrowing: The range is contained for normal limits.
template <typename Dst, template <typename Dst,
typename Src, typename Src,
template <typename> class Bounds,
IntegerRepresentation DstSign, IntegerRepresentation DstSign,
IntegerRepresentation SrcSign> IntegerRepresentation SrcSign>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<Dst,
Src, Src,
Bounds,
DstSign, DstSign,
SrcSign, SrcSign,
NUMERIC_RANGE_CONTAINED> { NUMERIC_RANGE_CONTAINED> {
static constexpr RangeConstraint Check(Src value) { return RANGE_VALID; } static constexpr RangeConstraint Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeConstraint(
static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() ||
static_cast<Dst>(value) <= DstLimits::max(),
static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() ||
static_cast<Dst>(value) >= DstLimits::lowest());
}
}; };
// Signed to signed narrowing: Both the upper and lower boundaries may be // Signed to signed narrowing: Both the upper and lower boundaries may be
// exceeded. // exceeded for standard limits.
template <typename Dst, typename Src> template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<Dst,
Src, Src,
Bounds,
INTEGER_REPRESENTATION_SIGNED, INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_SIGNED, INTEGER_REPRESENTATION_SIGNED,
NUMERIC_RANGE_NOT_CONTAINED> { NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeConstraint Check(Src value) { static constexpr RangeConstraint Check(Src value) {
return GetRangeConstraint((value <= NarrowingRange<Dst, Src>::max()), using DstLimits = NarrowingRange<Dst, Src, Bounds>;
(value >= NarrowingRange<Dst, Src>::lowest())); return RangeConstraint(value <= DstLimits::max(),
value >= DstLimits::lowest());
} }
}; };
// Unsigned to unsigned narrowing: Only the upper boundary can be exceeded. // Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
template <typename Dst, typename Src> // standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<Dst,
Src, Src,
Bounds,
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_UNSIGNED,
NUMERIC_RANGE_NOT_CONTAINED> { NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeConstraint Check(Src value) { static constexpr RangeConstraint Check(Src value) {
return GetRangeConstraint(value <= NarrowingRange<Dst, Src>::max(), true); using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeConstraint(
value <= DstLimits::max(),
DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest());
} }
}; };
// Unsigned to signed: The upper boundary may be exceeded. // Unsigned to signed: Only the upper bound can be exceeded for standard limits.
template <typename Dst, typename Src> template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<Dst,
Src, Src,
Bounds,
INTEGER_REPRESENTATION_SIGNED, INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_UNSIGNED,
NUMERIC_RANGE_NOT_CONTAINED> { NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeConstraint Check(Src value) { static constexpr RangeConstraint Check(Src value) {
return IntegerBitsPlusSign<Dst>::value > IntegerBitsPlusSign<Src>::value using DstLimits = NarrowingRange<Dst, Src, Bounds>;
? RANGE_VALID using Promotion = decltype(Src() + Dst());
: GetRangeConstraint( return RangeConstraint(static_cast<Promotion>(value) <=
value <= static_cast<Src>(NarrowingRange<Dst, Src>::max()), static_cast<Promotion>(DstLimits::max()),
true); DstLimits::lowest() <= Dst(0) ||
static_cast<Promotion>(value) >=
static_cast<Promotion>(DstLimits::lowest()));
} }
}; };
// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst, // Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
// and any negative value exceeds the lower boundary. // and any negative value exceeds the lower boundary for standard limits.
template <typename Dst, typename Src> template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<Dst,
Src, Src,
Bounds,
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED, INTEGER_REPRESENTATION_SIGNED,
NUMERIC_RANGE_NOT_CONTAINED> { NUMERIC_RANGE_NOT_CONTAINED> {
static constexpr RangeConstraint Check(Src value) { static constexpr RangeConstraint Check(Src value) {
return (MaxExponent<Dst>::value >= MaxExponent<Src>::value) using SrcLimits = std::numeric_limits<Src>;
? GetRangeConstraint(true, value >= static_cast<Src>(0)) using DstLimits = NarrowingRange<Dst, Src, Bounds>;
: GetRangeConstraint( using Promotion = decltype(Src() + Dst());
value <= static_cast<Src>(NarrowingRange<Dst, Src>::max()), return RangeConstraint(
value >= static_cast<Src>(0)); static_cast<Promotion>(SrcLimits::max()) <=
static_cast<Promotion>(DstLimits::max()) ||
static_cast<Promotion>(value) <=
static_cast<Promotion>(DstLimits::max()),
value >= Src(0) && (DstLimits::lowest() == 0 ||
static_cast<Dst>(value) >= DstLimits::lowest()));
} }
}; };
template <typename Dst, typename Src> template <typename Dst,
template <typename> class Bounds = std::numeric_limits,
typename Src>
constexpr RangeConstraint DstRangeRelationToSrcRange(Src value) { constexpr RangeConstraint DstRangeRelationToSrcRange(Src value) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric."); static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric."); static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
return DstRangeRelationToSrcRangeImpl<Dst, Src>::Check(value); static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
} }
// Integer promotion templates used by the portable checked integer arithmetic. // Integer promotion templates used by the portable checked integer arithmetic.
......
...@@ -671,7 +671,14 @@ struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_NARROW> { ...@@ -671,7 +671,14 @@ struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_NARROW> {
TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1)); TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1)); TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1));
// Additional saturation tests.
EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max()));
EXPECT_EQ(DstLimits::lowest(), saturated_cast<Dst>(SrcLimits::lowest()));
if (SrcLimits::is_iec559) { if (SrcLimits::is_iec559) {
EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::quiet_NaN()));
TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1); TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1);
TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity()); TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1); TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
...@@ -714,6 +721,10 @@ struct TestNumericConversion<Dst, Src, UNSIGN_TO_SIGN_NARROW_OR_EQUAL> { ...@@ -714,6 +721,10 @@ struct TestNumericConversion<Dst, Src, UNSIGN_TO_SIGN_NARROW_OR_EQUAL> {
TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest()); TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1)); TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
// Additional saturation tests.
EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max()));
EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::lowest()));
} }
}; };
......
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