HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
numericCast.h
Go to the documentation of this file.
1 //
2 // Copyright 2024 Pixar
3 //
4 // Licensed under the terms set forth in the LICENSE.txt file available at
5 // https://openusd.org/license.
6 //
7 #ifndef PXR_BASE_GF_NUMERIC_CAST_H
8 #define PXR_BASE_GF_NUMERIC_CAST_H
9 
10 #include "pxr/pxr.h"
11 
12 #include "pxr/base/gf/traits.h"
13 #include "pxr/base/arch/pragmas.h"
14 
15 #include <cmath>
16 #include <limits>
17 #include <optional>
18 #include <type_traits>
19 
21 
22 /// Return true if integer \p t compares logically less-than integer \p u in a
23 /// mathematical sense. The comparison is safe against non-value-preserving
24 /// integral conversion.
25 ///
26 /// This mimics the C++20 std::cmp_less function for comparing integers of
27 /// different types where negative signed integers always compare less than (and
28 /// not equal to) unsigned integers.
29 template <class T, class U>
30 constexpr bool
31 GfIntegerCompareLess(T t, U u) noexcept
32 {
33  static_assert(std::is_integral_v<T> && !std::is_same_v<T, bool> &&
34  std::is_integral_v<U> && !std::is_same_v<U, bool>);
35 
36  if constexpr (std::is_signed_v<T> == std::is_signed_v<U>) {
37  return t < u;
38  }
39  else if constexpr (std::is_signed_v<T>) {
41  }
42  else {
43  return u >= 0 && t < std::make_unsigned_t<U>(u);
44  }
45 }
46 
48  GfNumericCastPosOverflow, ///< Value too high to convert.
49  GfNumericCastNegOverflow, ///< Value too low to convert.
50  GfNumericCastNaN ///< Value is a floating-point NaN.
51 };
52 
53 /// Attempt to convert \p from to a value of type \p To "safely". From and To
54 /// must be arithmetic types according to GfIsArithmetic -- either integral or
55 /// floating-point types (including GfHalf). Return a std::optional holding the
56 /// converted value if conversion succeeds, otherwise the empty optional. The
57 /// optional out-parameter \p failType can be used to determine why conversion
58 /// failed if desired.
59 ///
60 /// What "safely" means depends on the types From and To. If From and To are
61 /// both integral types other than bool, then \p from can safely convert to To
62 /// if \p from is in To's range. For example if \p from is an int32_t and To
63 /// is uint16_t, then \p from can successfully convert if it is in the range
64 /// [0, 65535].
65 ///
66 /// If To is an integral type other than bool and From is a floating-point type
67 /// (including GfHalf), then \p from can safely convert to To if it is neither
68 /// a NaN nor an infinity, and after truncation to integer its value is in To's
69 /// range, as above.
70 ///
71 /// If To is a bool, the converted value will be \c false if \p from is 0, and
72 /// \c true for all other values. If From is a bool, then the converted value
73 /// will be 0 if \p from is \c false or 1 if it is \c true. These conversions
74 /// are always safe and will always succeed. This matches C++ behavior for
75 /// bool conversions.
76 ///
77 /// Following boost::numeric_cast's behavior, no range checking is performed
78 /// converting from integral to floating-point or from floating-point to other
79 /// floating-point types. Note that converting an integral value that is out of
80 /// GfHalf's _finite_ range will produce a +/- inf GfHalf.
81 ///
82 template <class To, class From>
83 std::optional<To>
84 GfNumericCast(From from, GfNumericCastFailureType *failType = nullptr)
85 {
86  // Visual Studio emits warning C4756 (overflow in constant arithmetic)
87  // in certain cases. This appears to be a compiler bug, see:
88  // https://developercommunity.visualstudio.com/t/Warning-4756-when-casting-constant-doubl/10845906
89  //
90  // For now, just disable this warning.
91 #if defined(ARCH_COMPILER_MSVC)
93  ARCH_PRAGMA(warning(disable:4756))
94 #endif
95 
96  static_assert(GfIsArithmetic<From>::value &&
98 
99  using FromLimits = std::numeric_limits<From>;
100  using ToLimits = std::numeric_limits<To>;
101 
102  auto setFail = [&failType](GfNumericCastFailureType ft) {
103  if (failType) {
104  *failType = ft;
105  };
106  };
107 
108  // bool <-> int/float.
109  if constexpr (std::is_same_v<From, bool> ||
110  std::is_same_v<To, bool>) {
111  (void)setFail; // hush compiler.
112 
113  // static_cast is sufficient since this function follows the
114  // same bool conversion rules as C++ itself.
115  return static_cast<To>(from);
116  }
117  // int -> int.
118  else if constexpr (std::is_integral_v<From> &&
119  std::is_integral_v<To>) {
120  // Range check integer to integer.
121  if (GfIntegerCompareLess(from, ToLimits::min())) {
122  setFail(GfNumericCastNegOverflow);
123  return {};
124  }
125  if (GfIntegerCompareLess(ToLimits::max(), from)) {
126  setFail(GfNumericCastPosOverflow);
127  return {};
128  }
129  // In-range.
130  return static_cast<To>(from);
131  }
132  // float -> int.
133  else if constexpr (GfIsFloatingPoint<From>::value &&
134  std::is_integral_v<To>) {
135  // If the floating point value is NaN we cannot convert.
136  if (std::isnan(from)) {
137  setFail(GfNumericCastNaN);
138  return {};
139  }
140  // If the floating point value is an infinity we cannot convert.
141  if (std::isinf(from)) {
142  setFail(std::signbit(static_cast<double>(from))
145  return {};
146  }
147  // Otherwise the floating point value must be (when truncated) in the
148  // range for the To type. We do this by mapping the low/high values for
149  // To into From, then displacing these away from zero by 1 to account
150  // for the truncation, then checking against this range. Note this
151  // works okay for GfHalf whose max is ~65,000 when converting to
152  // int32_t, say. In that case we get a range like (-inf, inf), meaning
153  // that all finite halfs are in-range.
154  From low = static_cast<From>(ToLimits::lowest()) - static_cast<From>(1);
155  From high = static_cast<From>(ToLimits::max()) + static_cast<From>(1);
156 
157  if (from <= low) {
158  setFail(GfNumericCastNegOverflow);
159  return {};
160  }
161  if (from >= high) {
162  setFail(GfNumericCastPosOverflow);
163  return {};
164  }
165  // The value is in-range.
166  return static_cast<To>(from);
167  }
168  // float -> float, or float -> int.
169  else {
170  (void)setFail; // hush compiler.
171 
172  // No range checking, following boost::numeric_cast.
173  return static_cast<To>(from);
174  }
175 
176 #if defined(ARCH_COMPILER_MSVC)
178 #endif
179 }
180 
182 
183 #endif // PXR_BASE_GF_NUMERIC_CAST_H
GfNumericCastFailureType
Definition: numericCast.h:47
void
Definition: png.h:1083
#define PXR_NAMESPACE_OPEN_SCOPE
Definition: pxr.h:73
#define ARCH_PRAGMA_POP
Definition: pragmas.h:170
ImageBuf OIIO_API min(Image_or_Const A, Image_or_Const B, ROI roi={}, int nthreads=0)
Value too high to convert.
Definition: numericCast.h:48
FMT_INLINE FMT_CONSTEXPR bool signbit(T value)
Definition: format.h:2824
#define ARCH_PRAGMA_PUSH
Definition: pragmas.h:166
PXR_NAMESPACE_OPEN_SCOPE constexpr bool GfIntegerCompareLess(T t, U u) noexcept
Definition: numericCast.h:31
#define ARCH_PRAGMA
Definition: pragmas.h:174
std::optional< To > GfNumericCast(From from, GfNumericCastFailureType *failType=nullptr)
Definition: numericCast.h:84
GLdouble t
Definition: glad.h:2397
ImageBuf OIIO_API max(Image_or_Const A, Image_or_Const B, ROI roi={}, int nthreads=0)
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
Value is a floating-point NaN.
Definition: numericCast.h:50
Value too low to convert.
Definition: numericCast.h:49