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"
13
14#include <cmath>
15#include <limits>
16#include <optional>
17#include <type_traits>
18
19PXR_NAMESPACE_OPEN_SCOPE
20
28template <class T, class U>
29constexpr bool
30GfIntegerCompareLess(T t, U u) noexcept
31{
32 static_assert(std::is_integral_v<T> && std::is_integral_v<U>);
33
34 if constexpr (std::is_signed_v<T> == std::is_signed_v<U>) {
35 return t < u;
36 }
37 else if constexpr (std::is_signed_v<T>) {
38 return t < 0 || std::make_unsigned_t<T>(t) < u;
39 }
40 else {
41 return u >= 0 && t < std::make_unsigned_t<U>(u);
42 }
43}
44
45enum GfNumericCastFailureType {
46 GfNumericCastPosOverflow,
47 GfNumericCastNegOverflow,
48 GfNumericCastNaN
49};
50
73template <class To, class From>
74std::optional<To>
75GfNumericCast(From from, GfNumericCastFailureType *failType = nullptr)
76{
77 static_assert(GfIsArithmetic<From>::value &&
79
80 using FromLimits = std::numeric_limits<From>;
81 using ToLimits = std::numeric_limits<To>;
82
83 auto setFail = [&failType](GfNumericCastFailureType ft) {
84 if (failType) {
85 *failType = ft;
86 };
87 };
88
89 // int -> int.
90 if constexpr (std::is_integral_v<From> &&
91 std::is_integral_v<To>) {
92 // Range check integer to integer.
93 if (GfIntegerCompareLess(from, ToLimits::min())) {
94 setFail(GfNumericCastNegOverflow);
95 return {};
96 }
97 if (GfIntegerCompareLess(ToLimits::max(), from)) {
98 setFail(GfNumericCastPosOverflow);
99 return {};
100 }
101 // In-range.
102 return static_cast<To>(from);
103 }
104 // float -> int.
105 else if constexpr (GfIsFloatingPoint<From>::value &&
106 std::is_integral_v<To>) {
107 // If the floating point value is NaN we cannot convert.
108 if (std::isnan(from)) {
109 setFail(GfNumericCastNaN);
110 return {};
111 }
112 // If the floating point value is an infinity we cannot convert.
113 if (std::isinf(from)) {
114 setFail(std::signbit(static_cast<double>(from))
115 ? GfNumericCastNegOverflow
116 : GfNumericCastPosOverflow);
117 return {};
118 }
119 // Otherwise the floating point value must be (when truncated) in the
120 // range for the To type. We do this by mapping the low/high values for
121 // To into From, then displacing these away from zero by 1 to account
122 // for the truncation, then checking against this range. Note this
123 // works okay for GfHalf whose max is ~65,000 when converting to
124 // int32_t, say. In that case we get a range like (-inf, inf), meaning
125 // that all finite halfs are in-range.
126 From low = static_cast<From>(ToLimits::lowest()) - static_cast<From>(1);
127 From high = static_cast<From>(ToLimits::max()) + static_cast<From>(1);
128
129 if (from <= low) {
130 setFail(GfNumericCastNegOverflow);
131 return {};
132 }
133 if (from >= high) {
134 setFail(GfNumericCastPosOverflow);
135 return {};
136 }
137 // The value is in-range.
138 return static_cast<To>(from);
139 }
140 // float -> float, or float -> int.
141 else {
142 (void)setFail; // hush compiler.
143
144 // No range checking, following boost::numeric_cast.
145 return static_cast<To>(from);
146 }
147}
148
149PXR_NAMESPACE_CLOSE_SCOPE
150
151#endif // PXR_BASE_GF_NUMERIC_CAST_H
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