1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* Cast operations to supplement the built-in casting operations. */
9 #ifndef mozilla_Casting_h
10 #define mozilla_Casting_h
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Sprintf.h"
17 #include <type_traits>
24 * Sets the outparam value of type |To| with the same underlying bit pattern of
27 * |To| and |From| must be types of the same size; be careful of cross-platform
28 * size differences, or this might fail to compile on some but not all
31 * There is also a variant that returns the value directly. In most cases, the
32 * two variants should be identical. However, in the specific case of x86
33 * chips, the behavior differs: returning floating-point values directly is done
34 * through the x87 stack, and x87 loads and stores turn signaling NaNs into
35 * quiet NaNs... silently. Returning floating-point values via outparam,
36 * however, is done entirely within the SSE registers when SSE2 floating-point
37 * is enabled in the compiler, which has semantics-preserving behavior you would
40 * If preserving the distinction between signaling NaNs and quiet NaNs is
41 * important to you, you should use the outparam version. In all other cases,
42 * you should use the direct return version.
44 template <typename To
, typename From
>
45 inline void BitwiseCast(const From aFrom
, To
* aResult
) {
46 static_assert(sizeof(From
) == sizeof(To
),
47 "To and From must have the same size");
49 // We could maybe downgrade these to std::is_trivially_copyable, but the
50 // various STLs we use don't all provide it.
51 static_assert(std::is_trivial
<From
>::value
,
52 "shouldn't bitwise-copy a type having non-trivial "
54 static_assert(std::is_trivial
<To
>::value
,
55 "shouldn't bitwise-copy a type having non-trivial "
58 std::memcpy(static_cast<void*>(aResult
), static_cast<const void*>(&aFrom
),
62 template <typename To
, typename From
>
63 inline To
BitwiseCast(const From aFrom
) {
65 BitwiseCast
<To
, From
>(aFrom
, &temp
);
72 constexpr int64_t safe_integer() {
73 static_assert(std::is_floating_point_v
<T
>);
74 return std::pow(2, std::numeric_limits
<T
>::digits
);
78 constexpr uint64_t safe_integer_unsigned() {
79 static_assert(std::is_floating_point_v
<T
>);
80 return std::pow(2, std::numeric_limits
<T
>::digits
);
84 const char* TypeToString();
86 const char* TypeToFormatString();
88 #define T2S(type, formatstring) \
90 inline constexpr const char* TypeToString<type>() { \
94 inline constexpr const char* TypeToFormatString<type>() { \
95 return "%" formatstring; \
99 T2S(uint16_t, PRIu16
);
100 T2S(uint32_t, PRIu32
);
101 T2S(uint64_t, PRIu64
);
103 T2S(int16_t, PRId16
);
104 T2S(int32_t, PRId32
);
105 T2S(int64_t, PRId64
);
106 T2S(char16_t
, PRIu16
); // print as a number
107 T2S(char32_t
, PRIu32
); // print as a number
108 #if defined(XP_DARWIN) || defined(XP_WIN) || defined(__wasm__)
110 T2S(unsigned long, "lu");
117 template <typename In
, typename Out
>
118 inline void DiagnosticMessage(In aIn
, char aDiagnostic
[1024]) {
120 SprintfBuf(number
, 128, TypeToFormatString
<In
>(), aIn
);
121 SprintfBuf(aDiagnostic
, 1024, "Cannot cast %s from %s to %s: out of range",
122 number
, TypeToString
<In
>(), TypeToString
<Out
>());
125 // This is working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81676,
127 #pragma GCC diagnostic push
128 #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
129 template <typename In
, typename Out
>
130 bool IsInBounds(In aIn
) {
131 constexpr bool inSigned
= std::is_signed_v
<In
>;
132 constexpr bool outSigned
= std::is_signed_v
<Out
>;
133 constexpr bool bothSigned
= inSigned
&& outSigned
;
134 constexpr bool bothUnsigned
= !inSigned
&& !outSigned
;
135 constexpr bool inFloat
= std::is_floating_point_v
<In
>;
136 constexpr bool outFloat
= std::is_floating_point_v
<Out
>;
137 constexpr bool bothFloat
= inFloat
&& outFloat
;
138 constexpr bool noneFloat
= !inFloat
&& !outFloat
;
139 constexpr Out outMax
= std::numeric_limits
<Out
>::max();
140 constexpr Out outMin
= std::numeric_limits
<Out
>::lowest();
142 // This selects the widest of two types, and is used to cast throughout.
143 using select_widest
= std::conditional_t
<(sizeof(In
) > sizeof(Out
)), In
, Out
>;
145 if constexpr (bothFloat
) {
146 if (aIn
> select_widest(outMax
) || aIn
< select_widest(outMin
)) {
150 // Normal casting applies, the floating point number is floored.
151 if constexpr (inFloat
&& !outFloat
) {
152 static_assert(sizeof(aIn
) <= sizeof(int64_t));
153 // Check if the input floating point is larger than the output bounds. This
154 // catches situations where the input is a float larger than the max of the
156 if (aIn
< static_cast<double>(outMin
) ||
157 aIn
> static_cast<double>(outMax
)) {
160 // At this point we know that the input can be converted to an integer.
161 // Check if it's larger than the bounds of the target integer.
163 int64_t asInteger
= static_cast<int64_t>(aIn
);
164 if (asInteger
< outMin
|| asInteger
> outMax
) {
168 uint64_t asInteger
= static_cast<uint64_t>(aIn
);
169 if (asInteger
> outMax
) {
175 // Checks if the integer is representable exactly as a floating point value of
177 if constexpr (!inFloat
&& outFloat
) {
178 if constexpr (inSigned
) {
179 if (aIn
< -safe_integer
<Out
>() || aIn
> safe_integer
<Out
>()) {
183 if (aIn
>= safe_integer_unsigned
<Out
>()) {
189 if constexpr (noneFloat
) {
190 if constexpr (bothUnsigned
) {
191 if (aIn
> select_widest(outMax
)) {
195 if constexpr (bothSigned
) {
196 if (aIn
> select_widest(outMax
) || aIn
< select_widest(outMin
)) {
200 if constexpr (inSigned
&& !outSigned
) {
201 if (aIn
< 0 || std::make_unsigned_t
<In
>(aIn
) > outMax
) {
205 if constexpr (!inSigned
&& outSigned
) {
206 if (aIn
> select_widest(outMax
)) {
213 #pragma GCC diagnostic pop
215 } // namespace detail
218 * Cast a value of type |From| to a value of type |To|, asserting that the cast
219 * will be a safe cast per C++ (that is, that |to| is in the range of values
220 * permitted for the type |From|).
221 * In particular, this will fail if a integer cannot be represented exactly as a
222 * floating point value, because it's too large.
224 template <typename To
, typename From
>
225 inline To
AssertedCast(const From aFrom
) {
226 static_assert(std::is_arithmetic_v
<To
> && std::is_arithmetic_v
<From
>);
228 if (!detail::IsInBounds
<From
, To
>(aFrom
)) {
230 detail::DiagnosticMessage
<From
, To
>(aFrom
, buf
);
231 fprintf(stderr
, "AssertedCast error: %s\n", buf
);
235 return static_cast<To
>(aFrom
);
239 * Cast a value of numeric type |From| to a value of numeric type |To|, release
240 * asserting that the cast will be a safe cast per C++ (that is, that |to| is in
241 * the range of values permitted for the type |From|).
242 * In particular, this will fail if a integer cannot be represented exactly as a
243 * floating point value, because it's too large.
245 template <typename To
, typename From
>
246 inline To
ReleaseAssertedCast(const From aFrom
) {
247 static_assert(std::is_arithmetic_v
<To
> && std::is_arithmetic_v
<From
>);
248 MOZ_RELEASE_ASSERT((detail::IsInBounds
<From
, To
>(aFrom
)));
249 return static_cast<To
>(aFrom
);
253 * Cast from type From to type To, clamping to minimum and maximum value of the
254 * destination type if needed.
256 template <typename To
, typename From
>
257 inline To
SaturatingCast(const From aFrom
) {
258 static_assert(std::is_arithmetic_v
<To
> && std::is_arithmetic_v
<From
>);
259 // This implementation works up to 64-bits integers.
260 static_assert(sizeof(From
) <= 8 && sizeof(To
) <= 8);
261 constexpr bool fromFloat
= std::is_floating_point_v
<From
>;
262 constexpr bool toFloat
= std::is_floating_point_v
<To
>;
264 // It's not clear what the caller wants here, it could be round, truncate,
265 // closest value, etc.
266 static_assert((fromFloat
&& !toFloat
) || (!fromFloat
&& !toFloat
),
267 "Handle manually depending on desired behaviour");
269 // If the source is floating point and the destination isn't, it can be that
270 // casting changes the value unexpectedly. Casting to double and clamping to
271 // the max of the destination type is correct, this also handles infinity.
272 if constexpr (fromFloat
) {
273 if (aFrom
> static_cast<double>(std::numeric_limits
<To
>::max())) {
274 return std::numeric_limits
<To
>::max();
276 if (aFrom
< static_cast<double>(std::numeric_limits
<To
>::lowest())) {
277 return std::numeric_limits
<To
>::lowest();
279 return static_cast<To
>(aFrom
);
281 // Source and destination are of opposite signedness
282 if constexpr (std::is_signed_v
<From
> != std::is_signed_v
<To
>) {
283 // Input is negative, output is unsigned, return 0
284 if (std::is_signed_v
<From
> && aFrom
< 0) {
287 // At this point the input is positive, cast everything to uint64_t for
288 // simplicity and compare
289 uint64_t inflated
= AssertedCast
<uint64_t>(aFrom
);
290 if (inflated
> static_cast<uint64_t>(std::numeric_limits
<To
>::max())) {
291 return std::numeric_limits
<To
>::max();
293 return static_cast<To
>(aFrom
);
295 // Regular case: clamp to destination type range
296 if (aFrom
> std::numeric_limits
<To
>::max()) {
297 return std::numeric_limits
<To
>::max();
299 if (aFrom
< std::numeric_limits
<To
>::lowest()) {
300 return std::numeric_limits
<To
>::lowest();
302 return static_cast<To
>(aFrom
);
308 template <typename From
>
309 class LazyAssertedCastT final
{
313 explicit LazyAssertedCastT(const From val
) : mVal(val
) {}
315 template <typename To
>
316 operator To() const {
317 return AssertedCast
<To
>(mVal
);
321 } // namespace detail
324 * Like AssertedCast, but infers |To| for AssertedCast lazily based on usage.
325 * > uint8_t foo = LazyAssertedCast(1000); // boom
327 template <typename From
>
328 inline auto LazyAssertedCast(const From val
) {
329 return detail::LazyAssertedCastT
<From
>(val
);
332 } // namespace mozilla
334 #endif /* mozilla_Casting_h */