Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
danakj | 0a44860 | 2015-03-10 00:31:16 | [diff] [blame] | 5 | #ifndef BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ |
| 6 | #define BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 7 | |
Jean-Philippe Gravel | f81c63b | 2024-04-23 20:33:17 | [diff] [blame] | 8 | // IWYU pragma: private, include "base/numerics/safe_conversions.h" |
| 9 | |
| 10 | #include <stddef.h> |
avi | 9b6f4293 | 2015-12-26 22:15:14 | [diff] [blame] | 11 | #include <stdint.h> |
| 12 | |
Daniel Cheng | 7bc7811 | 2023-11-20 19:41:27 | [diff] [blame] | 13 | #include <concepts> |
rickyz | d2fe3e24 | 2016-05-10 13:47:37 | [diff] [blame] | 14 | #include <limits> |
thomasanderson | 61500f2 | 2016-08-31 01:30:37 | [diff] [blame] | 15 | #include <type_traits> |
Takuto Ikuta | 4d4c685 | 2024-12-09 11:16:02 | [diff] [blame] | 16 | #include <utility> |
rickyz | d2fe3e24 | 2016-05-10 13:47:37 | [diff] [blame] | 17 | |
Peter Kasting | 6d97074 | 2024-12-06 05:06:27 | [diff] [blame] | 18 | #include "base/numerics/integral_constant_like.h" |
| 19 | |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 20 | namespace base::internal { |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 21 | |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 22 | // The std library doesn't provide a binary max_exponent for integers, however |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 23 | // we can compute an analog using std::numeric_limits<>::digits. |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 24 | template <typename NumericType> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 25 | inline constexpr int kMaxExponent = |
| 26 | std::is_floating_point_v<NumericType> |
| 27 | ? std::numeric_limits<NumericType>::max_exponent |
| 28 | : std::numeric_limits<NumericType>::digits + 1; |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 29 | |
| 30 | // The number of bits (including the sign) in an integer. Eliminates sizeof |
| 31 | // hacks. |
| 32 | template <typename NumericType> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 33 | inline constexpr int kIntegerBitsPlusSign = |
| 34 | std::numeric_limits<NumericType>::digits + std::is_signed_v<NumericType>; |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 35 | |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 36 | // Determines if a numeric value is negative without throwing compiler |
| 37 | // warnings on: unsigned(value) < 0. |
Daniel Cheng | 7bc7811 | 2023-11-20 19:41:27 | [diff] [blame] | 38 | template <typename T> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 39 | requires(std::is_arithmetic_v<T>) |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 40 | constexpr bool IsValueNegative(T value) { |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 41 | if constexpr (std::is_signed_v<T>) { |
| 42 | return value < 0; |
| 43 | } else { |
| 44 | return false; |
| 45 | } |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 46 | } |
| 47 | |
| 48 | // This performs a fast negation, returning a signed value. It works on unsigned |
| 49 | // arguments, but probably doesn't do what you want for any unsigned value |
| 50 | // larger than max / 2 + 1 (i.e. signed min cast to unsigned). |
| 51 | template <typename T> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 52 | requires std::is_integral_v<T> |
| 53 | constexpr auto ConditionalNegate(T x, bool is_negative) { |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 54 | using SignedT = std::make_signed_t<T>; |
| 55 | using UnsignedT = std::make_unsigned_t<T>; |
Peter Kasting | 92964de | 2022-05-27 18:59:40 | [diff] [blame] | 56 | return static_cast<SignedT>((static_cast<UnsignedT>(x) ^ |
| 57 | static_cast<UnsignedT>(-SignedT(is_negative))) + |
| 58 | is_negative); |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 59 | } |
| 60 | |
jschuh | 33232e0 | 2017-01-03 20:33:36 | [diff] [blame] | 61 | // This performs a safe, absolute value via unsigned overflow. |
jschuh | ba3c4f94 | 2016-12-10 14:03:55 | [diff] [blame] | 62 | template <typename T> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 63 | requires std::is_integral_v<T> |
| 64 | constexpr auto SafeUnsignedAbs(T value) { |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 65 | using UnsignedT = std::make_unsigned_t<T>; |
Scott Graham | d08e987 | 2020-01-22 18:24:45 | [diff] [blame] | 66 | return IsValueNegative(value) |
| 67 | ? static_cast<UnsignedT>(0u - static_cast<UnsignedT>(value)) |
| 68 | : static_cast<UnsignedT>(value); |
jschuh | ba3c4f94 | 2016-12-10 14:03:55 | [diff] [blame] | 69 | } |
| 70 | |
Justin Schuh | e2eefe61 | 2022-01-13 22:48:50 | [diff] [blame] | 71 | // TODO(jschuh): Debug builds don't reliably propagate constants, so we restrict |
| 72 | // some accelerated runtime paths to release builds until this can be forced |
| 73 | // with consteval support in C++20 or C++23. |
| 74 | #if defined(NDEBUG) |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 75 | inline constexpr bool kEnableAsmCode = true; |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 76 | #else |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 77 | inline constexpr bool kEnableAsmCode = false; |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 78 | #endif |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 79 | |
Peter Boström | 5411965 | 2024-11-14 00:16:38 | [diff] [blame] | 80 | // Forces a crash, like a NOTREACHED(). Used for numeric boundary errors. |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 81 | // Also used in a constexpr template to trigger a compilation failure on |
| 82 | // an error condition. |
| 83 | struct CheckOnFailure { |
| 84 | template <typename T> |
| 85 | static T HandleFailure() { |
Justin Schuh | 680caa47 | 2017-08-29 02:44:07 | [diff] [blame] | 86 | #if defined(_MSC_VER) |
| 87 | __debugbreak(); |
| 88 | #elif defined(__GNUC__) || defined(__clang__) |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 89 | __builtin_trap(); |
| 90 | #else |
| 91 | ((void)(*(volatile char*)0 = 0)); |
| 92 | #endif |
| 93 | return T(); |
| 94 | } |
| 95 | }; |
| 96 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 97 | enum class IntegerRepresentation { kUnsigned, kSigned }; |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 98 | |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 99 | // A range for a given nunmeric Src type is contained for a given numeric Dst |
| 100 | // type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 101 | // numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true. |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 102 | // We implement this as template specializations rather than simple static |
| 103 | // comparisons to ensure type correctness in our comparisons. |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 104 | enum class NumericRangeRepresentation { kNotContained, kContained }; |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 105 | |
| 106 | // Helper templates to statically determine if our destination type can contain |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 107 | // maximum and minimum values represented by the source type. |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 108 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 109 | // Default case, used for same sign: Dst is guaranteed to contain Src only if |
| 110 | // its range is equal or larger. |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 111 | template <typename Dst, |
| 112 | typename Src, |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 113 | IntegerRepresentation DstSign = |
| 114 | std::is_signed_v<Dst> ? IntegerRepresentation::kSigned |
| 115 | : IntegerRepresentation::kUnsigned, |
| 116 | IntegerRepresentation SrcSign = |
| 117 | std::is_signed_v<Src> ? IntegerRepresentation::kSigned |
| 118 | : IntegerRepresentation::kUnsigned> |
| 119 | inline constexpr auto kStaticDstRangeRelationToSrcRange = |
| 120 | kMaxExponent<Dst> >= kMaxExponent<Src> |
| 121 | ? NumericRangeRepresentation::kContained |
| 122 | : NumericRangeRepresentation::kNotContained; |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 123 | |
| 124 | // Unsigned to signed: Dst is guaranteed to contain source only if its range is |
| 125 | // larger. |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 126 | template <typename Dst, typename Src> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 127 | inline constexpr auto |
| 128 | kStaticDstRangeRelationToSrcRange<Dst, |
| 129 | Src, |
| 130 | IntegerRepresentation::kSigned, |
| 131 | IntegerRepresentation::kUnsigned> = |
| 132 | kMaxExponent<Dst> > kMaxExponent<Src> |
| 133 | ? NumericRangeRepresentation::kContained |
| 134 | : NumericRangeRepresentation::kNotContained; |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 135 | |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 136 | // Signed to unsigned: Dst cannot be statically determined to contain Src. |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 137 | template <typename Dst, typename Src> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 138 | inline constexpr auto |
| 139 | kStaticDstRangeRelationToSrcRange<Dst, |
| 140 | Src, |
| 141 | IntegerRepresentation::kUnsigned, |
| 142 | IntegerRepresentation::kSigned> = |
| 143 | NumericRangeRepresentation::kNotContained; |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 144 | |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 145 | // This class wraps the range constraints as separate booleans so the compiler |
| 146 | // can identify constants and eliminate unused code paths. |
| 147 | class RangeCheck { |
| 148 | public: |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 149 | constexpr RangeCheck() = default; |
jschuh | 082b2ba | 2017-01-06 11:40:54 | [diff] [blame] | 150 | constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound) |
jschuh | c8f03cd | 2017-01-05 03:40:51 | [diff] [blame] | 151 | : is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {} |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 152 | |
| 153 | constexpr bool operator==(const RangeCheck& rhs) const = default; |
| 154 | |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 155 | constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; } |
| 156 | constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; } |
| 157 | constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; } |
| 158 | constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; } |
jschuh | c8f03cd | 2017-01-05 03:40:51 | [diff] [blame] | 159 | constexpr bool IsOverflowFlagSet() const { return is_overflow_; } |
| 160 | constexpr bool IsUnderflowFlagSet() const { return is_underflow_; } |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 161 | |
| 162 | private: |
jschuh | c8f03cd | 2017-01-05 03:40:51 | [diff] [blame] | 163 | // Do not change the order of these member variables. The integral conversion |
| 164 | // optimization depends on this exact order. |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 165 | const bool is_underflow_ = false; |
| 166 | const bool is_overflow_ = false; |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 167 | }; |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 168 | |
jschuh | fafe071 | 2015-09-14 20:21:24 | [diff] [blame] | 169 | // The following helper template addresses a corner case in range checks for |
| 170 | // conversion from a floating-point type to an integral type of smaller range |
| 171 | // but larger precision (e.g. float -> unsigned). The problem is as follows: |
| 172 | // 1. Integral maximum is always one less than a power of two, so it must be |
| 173 | // truncated to fit the mantissa of the floating point. The direction of |
| 174 | // rounding is implementation defined, but by default it's always IEEE |
| 175 | // floats, which round to nearest and thus result in a value of larger |
| 176 | // magnitude than the integral value. |
| 177 | // Example: float f = UINT_MAX; // f is 4294967296f but UINT_MAX |
| 178 | // // is 4294967295u. |
| 179 | // 2. If the floating point value is equal to the promoted integral maximum |
| 180 | // value, a range check will erroneously pass. |
| 181 | // Example: (4294967296f <= 4294967295u) // This is true due to a precision |
| 182 | // // loss in rounding up to float. |
| 183 | // 3. When the floating point value is then converted to an integral, the |
| 184 | // resulting value is out of range for the target integral type and |
| 185 | // thus is implementation defined. |
| 186 | // Example: unsigned u = (float)INT_MAX; // u will typically overflow to 0. |
| 187 | // To fix this bug we manually truncate the maximum value when the destination |
| 188 | // type is an integral of larger precision than the source floating-point type, |
| 189 | // such that the resulting maximum is represented exactly as a floating point. |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 190 | template <typename Dst, typename Src, template <typename> class Bounds> |
jschuh | fafe071 | 2015-09-14 20:21:24 | [diff] [blame] | 191 | struct NarrowingRange { |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 192 | using SrcLimits = std::numeric_limits<Src>; |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 193 | using DstLimits = std::numeric_limits<Dst>; |
jschuh | fafe071 | 2015-09-14 20:21:24 | [diff] [blame] | 194 | |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 195 | // Computes the mask required to make an accurate comparison between types. |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 196 | static constexpr int kShift = (kMaxExponent<Src> > kMaxExponent<Dst> && |
| 197 | SrcLimits::digits < DstLimits::digits) |
| 198 | ? (DstLimits::digits - SrcLimits::digits) |
| 199 | : 0; |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 200 | |
Daniel Cheng | 7bc7811 | 2023-11-20 19:41:27 | [diff] [blame] | 201 | template <typename T> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 202 | requires(std::same_as<T, Dst> && |
| 203 | ((std::integral<T> && kShift < DstLimits::digits) || |
| 204 | (std::floating_point<T> && kShift == 0))) |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 205 | // Masks out the integer bits that are beyond the precision of the |
| 206 | // intermediate type used for comparison. |
| 207 | static constexpr T Adjust(T value) { |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 208 | if constexpr (std::integral<T>) { |
| 209 | using UnsignedDst = typename std::make_unsigned_t<T>; |
| 210 | return static_cast<T>( |
| 211 | ConditionalNegate(SafeUnsignedAbs(value) & |
| 212 | ~((UnsignedDst{1} << kShift) - UnsignedDst{1}), |
| 213 | IsValueNegative(value))); |
| 214 | } else { |
| 215 | return value; |
| 216 | } |
jschuh | 71b669a | 2016-12-17 01:13:31 | [diff] [blame] | 217 | } |
| 218 | |
| 219 | static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); } |
| 220 | static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); } |
jschuh | fafe071 | 2015-09-14 20:21:24 | [diff] [blame] | 221 | }; |
| 222 | |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 223 | // The following templates are for ranges that must be verified at runtime. We |
| 224 | // split it into checks based on signedness to avoid confusing casts and |
| 225 | // compiler warnings on signed an unsigned comparisons. |
| 226 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 227 | // Default case, used for same sign narrowing: The range is contained for normal |
| 228 | // limits. |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 229 | template <typename Dst, |
| 230 | typename Src, |
Yuki Shiino | ea1c623 | 2022-04-14 11:05:01 | [diff] [blame] | 231 | template <typename> |
| 232 | class Bounds, |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 233 | IntegerRepresentation DstSign = |
| 234 | std::is_signed_v<Dst> ? IntegerRepresentation::kSigned |
| 235 | : IntegerRepresentation::kUnsigned, |
| 236 | IntegerRepresentation SrcSign = |
| 237 | std::is_signed_v<Src> ? IntegerRepresentation::kSigned |
| 238 | : IntegerRepresentation::kUnsigned, |
| 239 | NumericRangeRepresentation DstRange = |
| 240 | kStaticDstRangeRelationToSrcRange<Dst, Src>> |
| 241 | struct DstRangeRelationToSrcRangeImpl { |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 242 | static constexpr RangeCheck Check(Src value) { |
| 243 | using SrcLimits = std::numeric_limits<Src>; |
| 244 | using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| 245 | return RangeCheck( |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 246 | static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() || |
jschuh | 082b2ba | 2017-01-06 11:40:54 | [diff] [blame] | 247 | static_cast<Dst>(value) >= DstLimits::lowest(), |
| 248 | static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() || |
| 249 | static_cast<Dst>(value) <= DstLimits::max()); |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 250 | } |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 251 | }; |
| 252 | |
| 253 | // Signed to signed narrowing: Both the upper and lower boundaries may be |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 254 | // exceeded for standard limits. |
| 255 | template <typename Dst, typename Src, template <typename> class Bounds> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 256 | struct DstRangeRelationToSrcRangeImpl< |
| 257 | Dst, |
| 258 | Src, |
| 259 | Bounds, |
| 260 | IntegerRepresentation::kSigned, |
| 261 | IntegerRepresentation::kSigned, |
| 262 | NumericRangeRepresentation::kNotContained> { |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 263 | static constexpr RangeCheck Check(Src value) { |
| 264 | using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
jschuh | 082b2ba | 2017-01-06 11:40:54 | [diff] [blame] | 265 | return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max()); |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 266 | } |
| 267 | }; |
| 268 | |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 269 | // Unsigned to unsigned narrowing: Only the upper bound can be exceeded for |
| 270 | // standard limits. |
| 271 | template <typename Dst, typename Src, template <typename> class Bounds> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 272 | struct DstRangeRelationToSrcRangeImpl< |
| 273 | Dst, |
| 274 | Src, |
| 275 | Bounds, |
| 276 | IntegerRepresentation::kUnsigned, |
| 277 | IntegerRepresentation::kUnsigned, |
| 278 | NumericRangeRepresentation::kNotContained> { |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 279 | static constexpr RangeCheck Check(Src value) { |
| 280 | using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| 281 | return RangeCheck( |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 282 | DstLimits::lowest() == Dst{0} || value >= DstLimits::lowest(), |
jschuh | 082b2ba | 2017-01-06 11:40:54 | [diff] [blame] | 283 | value <= DstLimits::max()); |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 284 | } |
| 285 | }; |
| 286 | |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 287 | // Unsigned to signed: Only the upper bound can be exceeded for standard limits. |
| 288 | template <typename Dst, typename Src, template <typename> class Bounds> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 289 | struct DstRangeRelationToSrcRangeImpl< |
| 290 | Dst, |
| 291 | Src, |
| 292 | Bounds, |
| 293 | IntegerRepresentation::kSigned, |
| 294 | IntegerRepresentation::kUnsigned, |
| 295 | NumericRangeRepresentation::kNotContained> { |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 296 | static constexpr RangeCheck Check(Src value) { |
| 297 | using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| 298 | using Promotion = decltype(Src() + Dst()); |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 299 | return RangeCheck(DstLimits::lowest() <= Dst{0} || |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 300 | static_cast<Promotion>(value) >= |
jschuh | 082b2ba | 2017-01-06 11:40:54 | [diff] [blame] | 301 | static_cast<Promotion>(DstLimits::lowest()), |
| 302 | static_cast<Promotion>(value) <= |
| 303 | static_cast<Promotion>(DstLimits::max())); |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 304 | } |
| 305 | }; |
| 306 | |
[email protected] | 5bfecbc | 2014-02-27 13:49:04 | [diff] [blame] | 307 | // Signed to unsigned: The upper boundary may be exceeded for a narrower Dst, |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 308 | // and any negative value exceeds the lower boundary for standard limits. |
| 309 | template <typename Dst, typename Src, template <typename> class Bounds> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 310 | struct DstRangeRelationToSrcRangeImpl< |
| 311 | Dst, |
| 312 | Src, |
| 313 | Bounds, |
| 314 | IntegerRepresentation::kUnsigned, |
| 315 | IntegerRepresentation::kSigned, |
| 316 | NumericRangeRepresentation::kNotContained> { |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 317 | static constexpr RangeCheck Check(Src value) { |
| 318 | using SrcLimits = std::numeric_limits<Src>; |
| 319 | using DstLimits = NarrowingRange<Dst, Src, Bounds>; |
| 320 | using Promotion = decltype(Src() + Dst()); |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 321 | bool ge_zero; |
Ng Zhi An | 280ed26 | 2020-10-22 00:10:12 | [diff] [blame] | 322 | // Converting floating-point to integer will discard fractional part, so |
| 323 | // values in (-1.0, -0.0) will truncate to 0 and fit in Dst. |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 324 | if constexpr (std::is_floating_point_v<Src>) { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 325 | ge_zero = value > Src{-1}; |
Ng Zhi An | 280ed26 | 2020-10-22 00:10:12 | [diff] [blame] | 326 | } else { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 327 | ge_zero = value >= Src{0}; |
Ng Zhi An | 280ed26 | 2020-10-22 00:10:12 | [diff] [blame] | 328 | } |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 329 | return RangeCheck( |
Ng Zhi An | 280ed26 | 2020-10-22 00:10:12 | [diff] [blame] | 330 | ge_zero && (DstLimits::lowest() == 0 || |
| 331 | static_cast<Dst>(value) >= DstLimits::lowest()), |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 332 | static_cast<Promotion>(SrcLimits::max()) <= |
| 333 | static_cast<Promotion>(DstLimits::max()) || |
| 334 | static_cast<Promotion>(value) <= |
jschuh | 082b2ba | 2017-01-06 11:40:54 | [diff] [blame] | 335 | static_cast<Promotion>(DstLimits::max())); |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 336 | } |
| 337 | }; |
| 338 | |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 339 | // Simple wrapper for statically checking if a type's range is contained. |
| 340 | template <typename Dst, typename Src> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 341 | inline constexpr bool kIsTypeInRangeForNumericType = |
| 342 | kStaticDstRangeRelationToSrcRange<Dst, Src> == |
| 343 | NumericRangeRepresentation::kContained; |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 344 | |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 345 | template <typename Dst, |
| 346 | template <typename> class Bounds = std::numeric_limits, |
| 347 | typename Src> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 348 | requires(std::is_arithmetic_v<Src> && std::is_arithmetic_v<Dst> && |
| 349 | Bounds<Dst>::lowest() < Bounds<Dst>::max()) |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 350 | constexpr RangeCheck DstRangeRelationToSrcRange(Src value) { |
jschuh | e3bd1f6 | 2016-12-20 05:11:30 | [diff] [blame] | 351 | return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value); |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 352 | } |
| 353 | |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 354 | // Integer promotion templates used by the portable checked integer arithmetic. |
| 355 | template <size_t Size, bool IsSigned> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 356 | struct IntegerForDigitsAndSignImpl; |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 357 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 358 | #define INTEGER_FOR_DIGITS_AND_SIGN(I) \ |
| 359 | template <> \ |
| 360 | struct IntegerForDigitsAndSignImpl<kIntegerBitsPlusSign<I>, \ |
| 361 | std::is_signed_v<I>> { \ |
| 362 | using type = I; \ |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 363 | } |
| 364 | |
| 365 | INTEGER_FOR_DIGITS_AND_SIGN(int8_t); |
| 366 | INTEGER_FOR_DIGITS_AND_SIGN(uint8_t); |
| 367 | INTEGER_FOR_DIGITS_AND_SIGN(int16_t); |
| 368 | INTEGER_FOR_DIGITS_AND_SIGN(uint16_t); |
| 369 | INTEGER_FOR_DIGITS_AND_SIGN(int32_t); |
| 370 | INTEGER_FOR_DIGITS_AND_SIGN(uint32_t); |
| 371 | INTEGER_FOR_DIGITS_AND_SIGN(int64_t); |
| 372 | INTEGER_FOR_DIGITS_AND_SIGN(uint64_t); |
| 373 | #undef INTEGER_FOR_DIGITS_AND_SIGN |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 374 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 375 | template <size_t Size, bool IsSigned> |
| 376 | using IntegerForDigitsAndSign = |
| 377 | IntegerForDigitsAndSignImpl<Size, IsSigned>::type; |
| 378 | |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 379 | // WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to |
| 380 | // support 128-bit math, then the ArithmeticPromotion template below will need |
| 381 | // to be updated (or more likely replaced with a decltype expression). |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 382 | static_assert(kIntegerBitsPlusSign<intmax_t> == 64, |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 383 | "Max integer size not supported for this toolchain."); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 384 | |
Andrew Rayskiy | c817c3cf | 2023-10-16 18:15:40 | [diff] [blame] | 385 | template <typename Integer, bool IsSigned = std::is_signed_v<Integer>> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 386 | using TwiceWiderInteger = |
| 387 | IntegerForDigitsAndSign<kIntegerBitsPlusSign<Integer> * 2, IsSigned>; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 388 | |
jschuh | 711ac6a | 2016-12-04 07:17:48 | [diff] [blame] | 389 | // Determines the type that can represent the largest positive value. |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 390 | template <typename Lhs, typename Rhs> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 391 | using MaxExponentPromotion = |
| 392 | std::conditional_t<(kMaxExponent<Lhs> > kMaxExponent<Rhs>), Lhs, Rhs>; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 393 | |
jschuh | 711ac6a | 2016-12-04 07:17:48 | [diff] [blame] | 394 | // Determines the type that can represent the lowest arithmetic value. |
jschuh | 711ac6a | 2016-12-04 07:17:48 | [diff] [blame] | 395 | template <typename Lhs, typename Rhs> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 396 | using LowestValuePromotion = std::conditional_t< |
| 397 | std::is_signed_v<Lhs> |
| 398 | ? (!std::is_signed_v<Rhs> || kMaxExponent<Lhs> > kMaxExponent<Rhs>) |
| 399 | : (!std::is_signed_v<Rhs> && kMaxExponent<Lhs> < kMaxExponent<Rhs>), |
| 400 | Lhs, |
| 401 | Rhs>; |
jschuh | 711ac6a | 2016-12-04 07:17:48 | [diff] [blame] | 402 | |
| 403 | // Determines the type that is best able to represent an arithmetic result. |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 404 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 405 | // Default case, used when the side with the max exponent is big enough. |
| 406 | template <typename Lhs, |
| 407 | typename Rhs = Lhs, |
| 408 | bool is_intmax_type = |
| 409 | std::is_integral_v<MaxExponentPromotion<Lhs, Rhs>> && |
| 410 | kIntegerBitsPlusSign<MaxExponentPromotion<Lhs, Rhs>> == |
| 411 | kIntegerBitsPlusSign<intmax_t>, |
| 412 | bool is_max_exponent = |
| 413 | kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>, |
| 414 | Lhs> == |
| 415 | NumericRangeRepresentation::kContained && |
| 416 | kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>, |
| 417 | Rhs> == |
| 418 | NumericRangeRepresentation::kContained> |
| 419 | struct BigEnoughPromotionImpl { |
| 420 | using type = MaxExponentPromotion<Lhs, Rhs>; |
| 421 | static constexpr bool kContained = true; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 422 | }; |
| 423 | |
| 424 | // We can use a twice wider type to fit. |
| 425 | template <typename Lhs, typename Rhs> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 426 | struct BigEnoughPromotionImpl<Lhs, Rhs, false, false> { |
jschuh | 5030b00c | 2016-12-05 18:21:48 | [diff] [blame] | 427 | using type = |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 428 | TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>, |
| 429 | std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>; |
| 430 | static constexpr bool kContained = true; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 431 | }; |
| 432 | |
| 433 | // No type is large enough. |
| 434 | template <typename Lhs, typename Rhs> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 435 | struct BigEnoughPromotionImpl<Lhs, Rhs, true, false> { |
| 436 | using type = MaxExponentPromotion<Lhs, Rhs>; |
| 437 | static constexpr bool kContained = false; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 438 | }; |
| 439 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 440 | template <typename Lhs, typename Rhs> |
| 441 | using BigEnoughPromotion = BigEnoughPromotionImpl<Lhs, Rhs>::type; |
| 442 | |
| 443 | template <typename Lhs, typename Rhs> |
| 444 | inline constexpr bool kIsBigEnoughPromotionContained = |
| 445 | BigEnoughPromotionImpl<Lhs, Rhs>::kContained; |
| 446 | |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 447 | // We can statically check if operations on the provided types can wrap, so we |
| 448 | // can skip the checked operations if they're not needed. So, for an integer we |
| 449 | // care if the destination type preserves the sign and is twice the width of |
| 450 | // the source. |
jschuh | 33232e0 | 2017-01-03 20:33:36 | [diff] [blame] | 451 | template <typename T, typename Lhs, typename Rhs = Lhs> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 452 | inline constexpr bool kIsIntegerArithmeticSafe = |
| 453 | !std::is_floating_point_v<T> && !std::is_floating_point_v<Lhs> && |
| 454 | !std::is_floating_point_v<Rhs> && |
| 455 | std::is_signed_v<T> >= std::is_signed_v<Lhs> && |
| 456 | kIntegerBitsPlusSign<T> >= |
| 457 | (2 * kIntegerBitsPlusSign<Lhs>)&&std::is_signed_v<T> >= |
| 458 | std::is_signed_v<Rhs> && |
| 459 | kIntegerBitsPlusSign<T> >= (2 * kIntegerBitsPlusSign<Rhs>); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 460 | |
jschuh | 33232e0 | 2017-01-03 20:33:36 | [diff] [blame] | 461 | // Promotes to a type that can represent any possible result of a binary |
| 462 | // arithmetic operation with the source types. |
Daniel Cheng | 7bc7811 | 2023-11-20 19:41:27 | [diff] [blame] | 463 | template <typename Lhs, typename Rhs> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 464 | struct FastIntegerArithmeticPromotionImpl { |
| 465 | using type = BigEnoughPromotion<Lhs, Rhs>; |
| 466 | static constexpr bool kContained = false; |
Daniel Cheng | 7bc7811 | 2023-11-20 19:41:27 | [diff] [blame] | 467 | }; |
jschuh | 33232e0 | 2017-01-03 20:33:36 | [diff] [blame] | 468 | |
| 469 | template <typename Lhs, typename Rhs> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 470 | requires(kIsIntegerArithmeticSafe< |
Daniel Cheng | 7bc7811 | 2023-11-20 19:41:27 | [diff] [blame] | 471 | std::conditional_t<std::is_signed_v<Lhs> || std::is_signed_v<Rhs>, |
| 472 | intmax_t, |
| 473 | uintmax_t>, |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 474 | MaxExponentPromotion<Lhs, Rhs>>) |
| 475 | struct FastIntegerArithmeticPromotionImpl<Lhs, Rhs> { |
jschuh | 33232e0 | 2017-01-03 20:33:36 | [diff] [blame] | 476 | using type = |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 477 | TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>, |
| 478 | std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>; |
| 479 | static_assert(kIsIntegerArithmeticSafe<type, Lhs, Rhs>); |
| 480 | static constexpr bool kContained = true; |
jschuh | 33232e0 | 2017-01-03 20:33:36 | [diff] [blame] | 481 | }; |
| 482 | |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 483 | template <typename Lhs, typename Rhs> |
| 484 | using FastIntegerArithmeticPromotion = |
| 485 | FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::type; |
| 486 | |
| 487 | template <typename Lhs, typename Rhs> |
| 488 | inline constexpr bool kIsFastIntegerArithmeticPromotionContained = |
| 489 | FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::kContained; |
| 490 | |
Peter Kasting | 6d97074 | 2024-12-06 05:06:27 | [diff] [blame] | 491 | template <typename T> |
| 492 | struct ArithmeticOrIntegralConstant { |
| 493 | using type = T; |
| 494 | }; |
| 495 | |
| 496 | template <typename T> |
| 497 | requires IntegralConstantLike<T> |
| 498 | struct ArithmeticOrIntegralConstant<T> { |
| 499 | using type = T::value_type; |
| 500 | }; |
| 501 | |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 502 | // Extracts the underlying type from an enum. |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 503 | template <typename T> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 504 | using ArithmeticOrUnderlyingEnum = |
| 505 | typename std::conditional_t<std::is_enum_v<T>, |
| 506 | std::underlying_type<T>, |
Peter Kasting | 6d97074 | 2024-12-06 05:06:27 | [diff] [blame] | 507 | ArithmeticOrIntegralConstant<T>>::type; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 508 | |
| 509 | // The following are helper templates used in the CheckedNumeric class. |
| 510 | template <typename T> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 511 | requires std::is_arithmetic_v<T> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 512 | class CheckedNumeric; |
| 513 | |
| 514 | template <typename T> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 515 | requires std::is_arithmetic_v<T> |
jschuh | 6a2c67b9 | 2017-06-12 22:57:22 | [diff] [blame] | 516 | class ClampedNumeric; |
| 517 | |
| 518 | template <typename T> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 519 | requires std::is_arithmetic_v<T> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 520 | class StrictNumeric; |
| 521 | |
| 522 | // Used to treat CheckedNumeric and arithmetic underlying types the same. |
| 523 | template <typename T> |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 524 | inline constexpr bool kIsCheckedNumeric = false; |
| 525 | template <typename T> |
| 526 | inline constexpr bool kIsCheckedNumeric<CheckedNumeric<T>> = true; |
| 527 | template <typename T> |
| 528 | concept IsCheckedNumeric = kIsCheckedNumeric<T>; |
| 529 | |
| 530 | template <typename T> |
| 531 | inline constexpr bool kIsClampedNumeric = false; |
| 532 | template <typename T> |
| 533 | inline constexpr bool kIsClampedNumeric<ClampedNumeric<T>> = true; |
| 534 | template <typename T> |
| 535 | concept IsClampedNumeric = kIsClampedNumeric<T>; |
| 536 | |
| 537 | template <typename T> |
| 538 | inline constexpr bool kIsStrictNumeric = false; |
| 539 | template <typename T> |
| 540 | inline constexpr bool kIsStrictNumeric<StrictNumeric<T>> = true; |
| 541 | template <typename T> |
| 542 | concept IsStrictNumeric = kIsStrictNumeric<T>; |
| 543 | |
| 544 | template <typename T> |
| 545 | struct UnderlyingTypeImpl { |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 546 | using type = ArithmeticOrUnderlyingEnum<T>; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 547 | }; |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 548 | template <typename T> |
| 549 | struct UnderlyingTypeImpl<CheckedNumeric<T>> { |
| 550 | using type = T; |
| 551 | }; |
| 552 | template <typename T> |
| 553 | struct UnderlyingTypeImpl<ClampedNumeric<T>> { |
| 554 | using type = T; |
| 555 | }; |
| 556 | template <typename T> |
| 557 | struct UnderlyingTypeImpl<StrictNumeric<T>> { |
| 558 | using type = T; |
| 559 | }; |
| 560 | template <typename T> |
| 561 | using UnderlyingType = UnderlyingTypeImpl<T>::type; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 562 | |
| 563 | template <typename T> |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 564 | inline constexpr bool kIsNumeric = std::is_arithmetic_v<UnderlyingType<T>>; |
jschuh | 6a2c67b9 | 2017-06-12 22:57:22 | [diff] [blame] | 565 | template <typename T> |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 566 | requires(IsCheckedNumeric<T> || IsClampedNumeric<T> || IsStrictNumeric<T>) |
| 567 | inline constexpr bool kIsNumeric<T> = true; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 568 | template <typename T> |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 569 | concept IsNumeric = kIsNumeric<T>; |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 570 | |
| 571 | template <typename L, typename R> |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 572 | concept IsCheckedOp = (IsCheckedNumeric<L> && IsNumeric<R>) || |
| 573 | (IsCheckedNumeric<R> && IsNumeric<L>); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 574 | |
| 575 | template <typename L, typename R> |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 576 | concept IsClampedOp = |
| 577 | !IsCheckedOp<L, R> && ((IsClampedNumeric<L> && IsNumeric<R>) || |
| 578 | (IsClampedNumeric<R> && IsNumeric<L>)); |
jschuh | 6a2c67b9 | 2017-06-12 22:57:22 | [diff] [blame] | 579 | |
| 580 | template <typename L, typename R> |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 581 | concept IsStrictOp = !IsCheckedOp<L, R> && !IsClampedOp<L, R> && |
| 582 | ((IsStrictNumeric<L> && IsNumeric<R>) || |
| 583 | (IsStrictNumeric<R> && IsNumeric<L>)); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 584 | |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 585 | // as_signed<> returns the supplied integral value (or integral castable |
| 586 | // Numeric template) cast as a signed integral of equivalent precision. |
| 587 | // I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t) |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 588 | template <typename Src, typename Dst = std::make_signed_t<UnderlyingType<Src>>> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 589 | requires std::integral<Dst> |
| 590 | constexpr auto as_signed(Src value) { |
| 591 | return static_cast<Dst>(value); |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 592 | } |
| 593 | |
| 594 | // as_unsigned<> returns the supplied integral value (or integral castable |
| 595 | // Numeric template) cast as an unsigned integral of equivalent precision. |
| 596 | // I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t) |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 597 | template <typename Src, |
Peter Kasting | a01d6b6 | 2024-12-05 20:19:59 | [diff] [blame] | 598 | typename Dst = std::make_unsigned_t<UnderlyingType<Src>>> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 599 | requires std::integral<Dst> |
| 600 | constexpr auto as_unsigned(Src value) { |
| 601 | return static_cast<Dst>(value); |
Justin Schuh | a26da27 | 2017-07-13 23:04:30 | [diff] [blame] | 602 | } |
| 603 | |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 604 | template <typename L, typename R> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 605 | requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 606 | struct IsLess { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 607 | using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| 608 | static constexpr bool Test(L lhs, R rhs) { |
| 609 | const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| 610 | const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| 611 | return l_range.IsUnderflow() || r_range.IsOverflow() || |
| 612 | (l_range == r_range && |
| 613 | static_cast<SumT>(lhs) < static_cast<SumT>(rhs)); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 614 | } |
| 615 | }; |
| 616 | |
| 617 | template <typename L, typename R> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 618 | requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 619 | struct IsLessOrEqual { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 620 | using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| 621 | static constexpr bool Test(L lhs, R rhs) { |
| 622 | const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| 623 | const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| 624 | return l_range.IsUnderflow() || r_range.IsOverflow() || |
| 625 | (l_range == r_range && |
| 626 | static_cast<SumT>(lhs) <= static_cast<SumT>(rhs)); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 627 | } |
| 628 | }; |
| 629 | |
| 630 | template <typename L, typename R> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 631 | requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 632 | struct IsGreater { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 633 | using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| 634 | static constexpr bool Test(L lhs, R rhs) { |
| 635 | const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| 636 | const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| 637 | return l_range.IsOverflow() || r_range.IsUnderflow() || |
| 638 | (l_range == r_range && |
| 639 | static_cast<SumT>(lhs) > static_cast<SumT>(rhs)); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 640 | } |
| 641 | }; |
| 642 | |
| 643 | template <typename L, typename R> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 644 | requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 645 | struct IsGreaterOrEqual { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 646 | using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| 647 | static constexpr bool Test(L lhs, R rhs) { |
| 648 | const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs); |
| 649 | const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs); |
| 650 | return l_range.IsOverflow() || r_range.IsUnderflow() || |
| 651 | (l_range == r_range && |
| 652 | static_cast<SumT>(lhs) >= static_cast<SumT>(rhs)); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 653 | } |
| 654 | }; |
| 655 | |
| 656 | template <typename L, typename R> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 657 | requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 658 | struct IsEqual { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 659 | using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| 660 | static constexpr bool Test(L lhs, R rhs) { |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 661 | return DstRangeRelationToSrcRange<R>(lhs) == |
| 662 | DstRangeRelationToSrcRange<L>(rhs) && |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 663 | static_cast<SumT>(lhs) == static_cast<SumT>(rhs); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 664 | } |
| 665 | }; |
| 666 | |
| 667 | template <typename L, typename R> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 668 | requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 669 | struct IsNotEqual { |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 670 | using SumT = decltype(std::declval<L>() + std::declval<R>()); |
| 671 | static constexpr bool Test(L lhs, R rhs) { |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 672 | return DstRangeRelationToSrcRange<R>(lhs) != |
| 673 | DstRangeRelationToSrcRange<L>(rhs) || |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 674 | static_cast<SumT>(lhs) != static_cast<SumT>(rhs); |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 675 | } |
| 676 | }; |
| 677 | |
| 678 | // These perform the actual math operations on the CheckedNumerics. |
| 679 | // Binary arithmetic operations. |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 680 | template <template <typename, typename> typename C, typename L, typename R> |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 681 | requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R> |
Peter Kasting | 5e7c9bf2 | 2024-11-13 01:25:22 | [diff] [blame] | 682 | constexpr bool SafeCompare(L lhs, R rhs) { |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 683 | using BigType = BigEnoughPromotion<L, R>; |
| 684 | return kIsBigEnoughPromotionContained<L, R> |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 685 | // Force to a larger type for speed if both are contained. |
Peter Kasting | baaa6ac0 | 2024-11-13 01:02:51 | [diff] [blame] | 686 | ? C<BigType, BigType>::Test(static_cast<BigType>(lhs), |
| 687 | static_cast<BigType>(rhs)) |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 688 | // Let the template functions figure it out for mixed types. |
| 689 | : C<L, R>::Test(lhs, rhs); |
jschuh | 6a2c67b9 | 2017-06-12 22:57:22 | [diff] [blame] | 690 | } |
jschuh | 23a4b06 | 2016-12-02 02:55:08 | [diff] [blame] | 691 | |
Justin Schuh | 5cc2abf | 2017-08-01 04:58:07 | [diff] [blame] | 692 | template <typename Dst, typename Src> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 693 | inline constexpr bool kIsMaxInRangeForNumericType = |
| 694 | IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(), |
| 695 | std::numeric_limits<Src>::max()); |
Justin Schuh | 5cc2abf | 2017-08-01 04:58:07 | [diff] [blame] | 696 | |
| 697 | template <typename Dst, typename Src> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 698 | inline constexpr bool kIsMinInRangeForNumericType = |
| 699 | IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(), |
| 700 | std::numeric_limits<Src>::lowest()); |
Justin Schuh | 5cc2abf | 2017-08-01 04:58:07 | [diff] [blame] | 701 | |
| 702 | template <typename Dst, typename Src> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 703 | inline constexpr Dst kCommonMax = |
| 704 | kIsMaxInRangeForNumericType<Dst, Src> |
| 705 | ? static_cast<Dst>(std::numeric_limits<Src>::max()) |
| 706 | : std::numeric_limits<Dst>::max(); |
Justin Schuh | 5cc2abf | 2017-08-01 04:58:07 | [diff] [blame] | 707 | |
| 708 | template <typename Dst, typename Src> |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 709 | inline constexpr Dst kCommonMin = |
| 710 | kIsMinInRangeForNumericType<Dst, Src> |
| 711 | ? static_cast<Dst>(std::numeric_limits<Src>::lowest()) |
| 712 | : std::numeric_limits<Dst>::lowest(); |
Justin Schuh | 5cc2abf | 2017-08-01 04:58:07 | [diff] [blame] | 713 | |
| 714 | // This is a wrapper to generate return the max or min for a supplied type. |
| 715 | // If the argument is false, the returned value is the maximum. If true the |
| 716 | // returned value is the minimum. |
| 717 | template <typename Dst, typename Src = Dst> |
| 718 | constexpr Dst CommonMaxOrMin(bool is_min) { |
Peter Kasting | abcf60a | 2024-11-13 01:24:37 | [diff] [blame] | 719 | return is_min ? kCommonMin<Dst, Src> : kCommonMax<Dst, Src>; |
Justin Schuh | 5cc2abf | 2017-08-01 04:58:07 | [diff] [blame] | 720 | } |
| 721 | |
Peter Kasting | 134183e | 2024-11-13 01:22:39 | [diff] [blame] | 722 | } // namespace base::internal |
[email protected] | 4efb2c3 | 2014-01-16 06:57:25 | [diff] [blame] | 723 | |
danakj | 0a44860 | 2015-03-10 00:31:16 | [diff] [blame] | 724 | #endif // BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ |