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