File: saturate_cast.h

package info (click to toggle)
android-platform-tools 34.0.5-12
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 150,900 kB
  • sloc: cpp: 805,786; java: 293,500; ansic: 128,288; xml: 127,491; python: 41,481; sh: 14,245; javascript: 9,665; cs: 3,846; asm: 2,049; makefile: 1,917; yacc: 440; awk: 368; ruby: 183; sql: 140; perl: 88; lex: 67
file content (145 lines) | stat: -rw-r--r-- 5,545 bytes parent folder | download | duplicates (11)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UTIL_SATURATE_CAST_H_
#define UTIL_SATURATE_CAST_H_

#include <cmath>
#include <limits>
#include <type_traits>

namespace openscreen {

// Case 0: When To and From are the same type, saturate_cast<> is pass-through.
template <typename To, typename From>
constexpr std::enable_if_t<
    std::is_same<std::remove_cv<To>, std::remove_cv<From>>::value,
    To>
saturate_cast(From from) {
  return from;
}

// Because of the way C++ signed versus unsigned comparison works (i.e., the
// type promotion strategy employed), extra care must be taken to range-check
// the input value. For example, if the current architecture is 32-bits, then
// any int32_t compared with a uint32_t will NOT promote to a int64_t↔int64_t
// comparison. Instead, it will become a uint32_t↔uint32_t comparison (!),
// which will sometimes produce invalid results.

// Case 1: "From" and "To" are either both signed, or are both unsigned. In
// this case, the smaller of the two types will be promoted to match the
// larger's size, and a valid comparison will be made.
template <typename To, typename From>
constexpr std::enable_if_t<
    std::is_integral<From>::value && std::is_integral<To>::value &&
        (std::is_signed<From>::value == std::is_signed<To>::value),
    To>
saturate_cast(From from) {
  if (from <= std::numeric_limits<To>::min()) {
    return std::numeric_limits<To>::min();
  }
  if (from >= std::numeric_limits<To>::max()) {
    return std::numeric_limits<To>::max();
  }
  return static_cast<To>(from);
}

// Case 2: "From" is signed, but "To" is unsigned.
template <typename To, typename From>
constexpr std::enable_if_t<
    std::is_integral<From>::value && std::is_integral<To>::value &&
        std::is_signed<From>::value && !std::is_signed<To>::value,
    To>
saturate_cast(From from) {
  if (from <= From{0}) {
    return To{0};
  }
  if (static_cast<std::make_unsigned_t<From>>(from) >=
      std::numeric_limits<To>::max()) {
    return std::numeric_limits<To>::max();
  }
  return static_cast<To>(from);
}

// Case 3: "From" is unsigned, but "To" is signed.
template <typename To, typename From>
constexpr std::enable_if_t<
    std::is_integral<From>::value && std::is_integral<To>::value &&
        !std::is_signed<From>::value && std::is_signed<To>::value,
    To>
saturate_cast(From from) {
  if (from >= static_cast<typename std::make_unsigned_t<To>>(
                  std::numeric_limits<To>::max())) {
    return std::numeric_limits<To>::max();
  }
  return static_cast<To>(from);
}

// Case 4: "From" is a floating-point type, and "To" is an integer type (signed
// or unsigned). The result is truncated, per the usual C++ float-to-int
// conversion rules.
template <typename To, typename From>
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
                               std::is_integral<To>::value,
                           To>
saturate_cast(From from) {
  // Note: It's invalid to compare the argument against
  // std::numeric_limits<To>::max() because the latter, an integer value, will
  // be type-promoted to the floating-point type. The problem is that the
  // conversion is imprecise, as "max int" might not be exactly representable as
  // a floating-point value (depending on the actual types of From and To).
  //
  // Thus, the strategy is to compare only floating-point values/constants to
  // determine whether the bounds of the range of integers has been exceeded.
  // Two assumptions here: 1) "To" is either unsigned, or is a 2's complement
  // signed integer type. 2) "From" is a floating-point type that can exactly
  // represent all powers of 2 within its value range.
  static_assert((~To(1) + To(1)) == To(-1), "assumed 2's complement integers");
  constexpr From kMaxIntPlusOne =
      From(To(1) << (std::numeric_limits<To>::digits - 1)) * From(2);
  constexpr From kMaxInt = kMaxIntPlusOne - 1;
  // Note: In some cases, the kMaxInt constant will equal kMaxIntPlusOne because
  // there isn't an exact floating-point representation for 2^N - 1. That said,
  // the following upper-bound comparison is still valid because all
  // floating-point values less than 2^N would also be less than 2^N - 1.
  if (from >= kMaxInt) {
    return std::numeric_limits<To>::max();
  }
  if (std::is_signed<To>::value) {
    constexpr From kMinInt = -kMaxIntPlusOne;
    if (from <= kMinInt) {
      return std::numeric_limits<To>::min();
    }
  } else /* if To is unsigned */ {
    if (from <= From(0)) {
      return To(0);
    }
  }
  return static_cast<To>(from);
}

// Like saturate_cast<>, but rounds to the nearest integer instead of
// truncating.
template <typename To, typename From>
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
                               std::is_integral<To>::value,
                           To>
rounded_saturate_cast(From from) {
  const To saturated = saturate_cast<To>(from);
  if (saturated == std::numeric_limits<To>::min() ||
      saturated == std::numeric_limits<To>::max()) {
    return saturated;
  }

  static_assert(sizeof(To) <= sizeof(decltype(llround(from))),
                "No version of lround() for the required range of values.");
  if (sizeof(To) <= sizeof(decltype(lround(from)))) {
    return static_cast<To>(lround(from));
  }
  return static_cast<To>(llround(from));
}

}  // namespace openscreen

#endif  // UTIL_SATURATE_CAST_H_