Loading...
Searching...
No Matches
numericCast.h
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"
14
15#include <cmath>
16#include <limits>
17#include <optional>
18#include <type_traits>
19
20PXR_NAMESPACE_OPEN_SCOPE
21
29template <class T, class U>
30constexpr bool
31GfIntegerCompareLess(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>) {
40 return t < 0 || std::make_unsigned_t<T>(t) < u;
41 }
42 else {
43 return u >= 0 && t < std::make_unsigned_t<U>(u);
44 }
45}
46
47enum GfNumericCastFailureType {
48 GfNumericCastPosOverflow,
49 GfNumericCastNegOverflow,
50 GfNumericCastNaN
51};
52
82template <class To, class From>
83std::optional<To>
84GfNumericCast(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)
92 ARCH_PRAGMA_PUSH;
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))
143 ? GfNumericCastNegOverflow
144 : GfNumericCastPosOverflow);
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)
177 ARCH_PRAGMA_POP;
178#endif
179}
180
181PXR_NAMESPACE_CLOSE_SCOPE
182
183#endif // PXR_BASE_GF_NUMERIC_CAST_H
Pragmas for controlling compiler-specific behaviors.
A metafunction which is equivalent to std::arithmetic but also includes any specializations from GfIs...
Definition: traits.h:51
A metafunction which is equivalent to std::is_floating_point but allows for additional specialization...
Definition: traits.h:45