1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_SATURATE_CAST_H_
#define UTIL_SATURATE_CAST_H_
#include <cmath>
#include <limits>
#include <type_traits>
namespace openscreen {
// Case 0: When To and From are the same type, saturate_cast<> is pass-through.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_same<std::remove_cv<To>, std::remove_cv<From>>::value,
To>
saturate_cast(From from) {
return from;
}
// Because of the way C++ signed versus unsigned comparison works (i.e., the
// type promotion strategy employed), extra care must be taken to range-check
// the input value. For example, if the current architecture is 32-bits, then
// any int32_t compared with a uint32_t will NOT promote to a int64_t↔int64_t
// comparison. Instead, it will become a uint32_t↔uint32_t comparison (!),
// which will sometimes produce invalid results.
// Case 1: "From" and "To" are either both signed, or are both unsigned. In
// this case, the smaller of the two types will be promoted to match the
// larger's size, and a valid comparison will be made.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_integral<From>::value && std::is_integral<To>::value &&
(std::is_signed<From>::value == std::is_signed<To>::value),
To>
saturate_cast(From from) {
if (from <= std::numeric_limits<To>::min()) {
return std::numeric_limits<To>::min();
}
if (from >= std::numeric_limits<To>::max()) {
return std::numeric_limits<To>::max();
}
return static_cast<To>(from);
}
// Case 2: "From" is signed, but "To" is unsigned.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_integral<From>::value && std::is_integral<To>::value &&
std::is_signed<From>::value && !std::is_signed<To>::value,
To>
saturate_cast(From from) {
if (from <= From{0}) {
return To{0};
}
if (static_cast<std::make_unsigned_t<From>>(from) >=
std::numeric_limits<To>::max()) {
return std::numeric_limits<To>::max();
}
return static_cast<To>(from);
}
// Case 3: "From" is unsigned, but "To" is signed.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_integral<From>::value && std::is_integral<To>::value &&
!std::is_signed<From>::value && std::is_signed<To>::value,
To>
saturate_cast(From from) {
if (from >= static_cast<typename std::make_unsigned_t<To>>(
std::numeric_limits<To>::max())) {
return std::numeric_limits<To>::max();
}
return static_cast<To>(from);
}
// Case 4: "From" is a floating-point type, and "To" is an integer type (signed
// or unsigned). The result is truncated, per the usual C++ float-to-int
// conversion rules.
template <typename To, typename From>
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
std::is_integral<To>::value,
To>
saturate_cast(From from) {
// Note: It's invalid to compare the argument against
// std::numeric_limits<To>::max() because the latter, an integer value, will
// be type-promoted to the floating-point type. The problem is that the
// conversion is imprecise, as "max int" might not be exactly representable as
// a floating-point value (depending on the actual types of From and To).
//
// Thus, the strategy is to compare only floating-point values/constants to
// determine whether the bounds of the range of integers has been exceeded.
// Two assumptions here: 1) "To" is either unsigned, or is a 2's complement
// signed integer type. 2) "From" is a floating-point type that can exactly
// represent all powers of 2 within its value range.
static_assert((~To(1) + To(1)) == To(-1), "assumed 2's complement integers");
constexpr From kMaxIntPlusOne =
From(To(1) << (std::numeric_limits<To>::digits - 1)) * From(2);
constexpr From kMaxInt = kMaxIntPlusOne - 1;
// Note: In some cases, the kMaxInt constant will equal kMaxIntPlusOne because
// there isn't an exact floating-point representation for 2^N - 1. That said,
// the following upper-bound comparison is still valid because all
// floating-point values less than 2^N would also be less than 2^N - 1.
if (from >= kMaxInt) {
return std::numeric_limits<To>::max();
}
if (std::is_signed<To>::value) {
constexpr From kMinInt = -kMaxIntPlusOne;
if (from <= kMinInt) {
return std::numeric_limits<To>::min();
}
} else /* if To is unsigned */ {
if (from <= From(0)) {
return To(0);
}
}
return static_cast<To>(from);
}
// Like saturate_cast<>, but rounds to the nearest integer instead of
// truncating.
template <typename To, typename From>
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
std::is_integral<To>::value,
To>
rounded_saturate_cast(From from) {
const To saturated = saturate_cast<To>(from);
if (saturated == std::numeric_limits<To>::min() ||
saturated == std::numeric_limits<To>::max()) {
return saturated;
}
static_assert(sizeof(To) <= sizeof(decltype(llround(from))),
"No version of lround() for the required range of values.");
if (sizeof(To) <= sizeof(decltype(lround(from)))) {
return static_cast<To>(lround(from));
}
return static_cast<To>(llround(from));
}
} // namespace openscreen
#endif // UTIL_SATURATE_CAST_H_
|