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