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