1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // Copyright (C) 2016 by Southwest Research Institute (R) |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #ifndef QFLOAT16_H |
6 | #define QFLOAT16_H |
7 | |
8 | #include <QtCore/qglobal.h> |
9 | #include <QtCore/qhashfunctions.h> |
10 | #include <QtCore/qmath.h> |
11 | #include <QtCore/qnamespace.h> |
12 | |
13 | #include <limits> |
14 | #include <string.h> |
15 | |
16 | #if defined(__STDCPP_FLOAT16_T__) && __has_include(<stdfloat>) |
17 | // P1467 implementation - https://wg21.link/p1467 |
18 | # include <stdfloat> |
19 | #endif |
20 | |
21 | #if defined(QT_COMPILER_SUPPORTS_F16C) && defined(__AVX2__) && !defined(__F16C__) |
22 | // All processors that support AVX2 do support F16C too, so we could enable the |
23 | // feature unconditionally if __AVX2__ is defined. However, all currently |
24 | // supported compilers except Microsoft's are able to define __F16C__ on their |
25 | // own when the user enables the feature, so we'll trust them. |
26 | # if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) |
27 | # define __F16C__ 1 |
28 | # endif |
29 | #endif |
30 | |
31 | #if defined(QT_COMPILER_SUPPORTS_F16C) && defined(__F16C__) |
32 | #include <immintrin.h> |
33 | #endif |
34 | |
35 | QT_BEGIN_NAMESPACE |
36 | |
37 | #if 0 |
38 | #pragma qt_class(QFloat16) |
39 | #pragma qt_no_master_include |
40 | #endif |
41 | |
42 | #ifndef QT_NO_DATASTREAM |
43 | class QDataStream; |
44 | #endif |
45 | class QTextStream; |
46 | |
47 | class qfloat16 |
48 | { |
49 | struct Wrap |
50 | { |
51 | // To let our private constructor work, without other code seeing |
52 | // ambiguity when constructing from int, double &c. |
53 | quint16 b16; |
54 | constexpr inline explicit Wrap(int value) : b16(quint16(value)) {} |
55 | }; |
56 | |
57 | public: |
58 | #if defined(__STDCPP_FLOAT16_T__) |
59 | # define QFLOAT16_IS_NATIVE 1 |
60 | using NativeType = std::float16_t; |
61 | #elif defined(Q_CC_CLANG) && defined(__FLT16_MAX__) && 0 |
62 | // disabled due to https://github.com/llvm/llvm-project/issues/56963 |
63 | # define QFLOAT16_IS_NATIVE 1 |
64 | using NativeType = decltype(__FLT16_MAX__); |
65 | #elif defined(Q_CC_GNU_ONLY) && defined(__FLT16_MAX__) |
66 | # define QFLOAT16_IS_NATIVE 1 |
67 | # ifdef __ARM_FP16_FORMAT_IEEE |
68 | using NativeType = __fp16; |
69 | # else |
70 | using NativeType = _Float16; |
71 | # endif |
72 | #else |
73 | # define QFLOAT16_IS_NATIVE 0 |
74 | using NativeType = void; |
75 | #endif |
76 | static constexpr bool IsNative = QFLOAT16_IS_NATIVE; |
77 | using NearestFloat = std::conditional_t<IsNative, NativeType, float>; |
78 | |
79 | constexpr inline qfloat16() noexcept : b16(0) {} |
80 | explicit qfloat16(Qt::Initialization) noexcept { } |
81 | |
82 | #if QFLOAT16_IS_NATIVE |
83 | constexpr inline qfloat16(NativeType f) : nf(f) {} |
84 | constexpr operator NativeType() const noexcept { return nf; } |
85 | #else |
86 | inline qfloat16(float f) noexcept; |
87 | inline operator float() const noexcept; |
88 | #endif |
89 | template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T> && !std::is_same_v<T, NearestFloat>>> |
90 | constexpr explicit qfloat16(T value) noexcept : qfloat16(NearestFloat(value)) {} |
91 | |
92 | // Support for qIs{Inf,NaN,Finite}: |
93 | bool isInf() const noexcept { return (b16 & 0x7fff) == 0x7c00; } |
94 | bool isNaN() const noexcept { return (b16 & 0x7fff) > 0x7c00; } |
95 | bool isFinite() const noexcept { return (b16 & 0x7fff) < 0x7c00; } |
96 | Q_CORE_EXPORT int fpClassify() const noexcept; |
97 | // Can't specialize std::copysign() for qfloat16 |
98 | qfloat16 copySign(qfloat16 sign) const noexcept |
99 | { return qfloat16(Wrap((sign.b16 & 0x8000) | (b16 & 0x7fff))); } |
100 | // Support for std::numeric_limits<qfloat16> |
101 | static constexpr qfloat16 _limit_epsilon() noexcept { return qfloat16(Wrap(0x1400)); } |
102 | static constexpr qfloat16 _limit_min() noexcept { return qfloat16(Wrap(0x400)); } |
103 | static constexpr qfloat16 _limit_denorm_min() noexcept { return qfloat16(Wrap(1)); } |
104 | static constexpr qfloat16 _limit_max() noexcept { return qfloat16(Wrap(0x7bff)); } |
105 | static constexpr qfloat16 _limit_lowest() noexcept { return qfloat16(Wrap(0xfbff)); } |
106 | static constexpr qfloat16 _limit_infinity() noexcept { return qfloat16(Wrap(0x7c00)); } |
107 | static constexpr qfloat16 _limit_quiet_NaN() noexcept { return qfloat16(Wrap(0x7e00)); } |
108 | #if QT_CONFIG(signaling_nan) |
109 | static constexpr qfloat16 _limit_signaling_NaN() noexcept { return qfloat16(Wrap(0x7d00)); } |
110 | #endif |
111 | inline constexpr bool isNormal() const noexcept |
112 | { return (b16 & 0x7c00) && (b16 & 0x7c00) != 0x7c00; } |
113 | private: |
114 | // ABI note: Qt 6's qfloat16 began with just a quint16 member so it ended |
115 | // up passed in general purpose registers in any function call taking |
116 | // qfloat16 by value (it has trivial copy constructors). This means the |
117 | // integer member in the anonymous union below must remain until a |
118 | // binary-incompatible version of Qt. If you remove it, on platforms using |
119 | // the System V ABI for C, the native type is passed in FP registers. |
120 | union { |
121 | quint16 b16; |
122 | #if QFLOAT16_IS_NATIVE |
123 | NativeType nf; |
124 | #endif |
125 | }; |
126 | constexpr inline explicit qfloat16(Wrap nibble) noexcept : b16(nibble.b16) {} |
127 | |
128 | Q_CORE_EXPORT static const quint32 mantissatable[]; |
129 | Q_CORE_EXPORT static const quint32 exponenttable[]; |
130 | Q_CORE_EXPORT static const quint32 offsettable[]; |
131 | Q_CORE_EXPORT static const quint16 basetable[]; |
132 | Q_CORE_EXPORT static const quint16 shifttable[]; |
133 | Q_CORE_EXPORT static const quint32 roundtable[]; |
134 | |
135 | friend bool qIsNull(qfloat16 f) noexcept; |
136 | |
137 | friend inline qfloat16 operator-(qfloat16 a) noexcept |
138 | { |
139 | qfloat16 f; |
140 | f.b16 = a.b16 ^ quint16(0x8000); |
141 | return f; |
142 | } |
143 | |
144 | friend inline qfloat16 operator+(qfloat16 a, qfloat16 b) noexcept { return qfloat16(static_cast<NearestFloat>(a) + static_cast<NearestFloat>(b)); } |
145 | friend inline qfloat16 operator-(qfloat16 a, qfloat16 b) noexcept { return qfloat16(static_cast<NearestFloat>(a) - static_cast<NearestFloat>(b)); } |
146 | friend inline qfloat16 operator*(qfloat16 a, qfloat16 b) noexcept { return qfloat16(static_cast<NearestFloat>(a) * static_cast<NearestFloat>(b)); } |
147 | friend inline qfloat16 operator/(qfloat16 a, qfloat16 b) noexcept { return qfloat16(static_cast<NearestFloat>(a) / static_cast<NearestFloat>(b)); } |
148 | |
149 | friend size_t qHash(qfloat16 key, size_t seed = 0) noexcept |
150 | { return qHash(key: float(key), seed); } // 6.4 algorithm, so keep using it; ### Qt 7: fix QTBUG-116077 |
151 | |
152 | #define QF16_MAKE_ARITH_OP_FP(FP, OP) \ |
153 | friend inline FP operator OP(qfloat16 lhs, FP rhs) noexcept { return static_cast<FP>(lhs) OP rhs; } \ |
154 | friend inline FP operator OP(FP lhs, qfloat16 rhs) noexcept { return lhs OP static_cast<FP>(rhs); } |
155 | #define QF16_MAKE_ARITH_OP_EQ_FP(FP, OP_EQ, OP) \ |
156 | friend inline qfloat16& operator OP_EQ(qfloat16& lhs, FP rhs) noexcept \ |
157 | { lhs = qfloat16(NearestFloat(static_cast<FP>(lhs) OP rhs)); return lhs; } |
158 | #define QF16_MAKE_ARITH_OP(FP) \ |
159 | QF16_MAKE_ARITH_OP_FP(FP, +) \ |
160 | QF16_MAKE_ARITH_OP_FP(FP, -) \ |
161 | QF16_MAKE_ARITH_OP_FP(FP, *) \ |
162 | QF16_MAKE_ARITH_OP_FP(FP, /) \ |
163 | QF16_MAKE_ARITH_OP_EQ_FP(FP, +=, +) \ |
164 | QF16_MAKE_ARITH_OP_EQ_FP(FP, -=, -) \ |
165 | QF16_MAKE_ARITH_OP_EQ_FP(FP, *=, *) \ |
166 | QF16_MAKE_ARITH_OP_EQ_FP(FP, /=, /) |
167 | |
168 | QF16_MAKE_ARITH_OP(long double) |
169 | QF16_MAKE_ARITH_OP(double) |
170 | QF16_MAKE_ARITH_OP(float) |
171 | #if QFLOAT16_IS_NATIVE |
172 | QF16_MAKE_ARITH_OP(NativeType) |
173 | #endif |
174 | #undef QF16_MAKE_ARITH_OP |
175 | #undef QF16_MAKE_ARITH_OP_FP |
176 | |
177 | #define QF16_MAKE_ARITH_OP_INT(OP) \ |
178 | friend inline double operator OP(qfloat16 lhs, int rhs) noexcept { return static_cast<double>(lhs) OP rhs; } \ |
179 | friend inline double operator OP(int lhs, qfloat16 rhs) noexcept { return lhs OP static_cast<double>(rhs); } |
180 | |
181 | QF16_MAKE_ARITH_OP_INT(+) |
182 | QF16_MAKE_ARITH_OP_INT(-) |
183 | QF16_MAKE_ARITH_OP_INT(*) |
184 | QF16_MAKE_ARITH_OP_INT(/) |
185 | #undef QF16_MAKE_ARITH_OP_INT |
186 | |
187 | QT_WARNING_PUSH |
188 | QT_WARNING_DISABLE_FLOAT_COMPARE |
189 | |
190 | friend inline bool operator>(qfloat16 a, qfloat16 b) noexcept { return static_cast<NearestFloat>(a) > static_cast<NearestFloat>(b); } |
191 | friend inline bool operator<(qfloat16 a, qfloat16 b) noexcept { return static_cast<NearestFloat>(a) < static_cast<NearestFloat>(b); } |
192 | friend inline bool operator>=(qfloat16 a, qfloat16 b) noexcept { return static_cast<NearestFloat>(a) >= static_cast<NearestFloat>(b); } |
193 | friend inline bool operator<=(qfloat16 a, qfloat16 b) noexcept { return static_cast<NearestFloat>(a) <= static_cast<NearestFloat>(b); } |
194 | friend inline bool operator==(qfloat16 a, qfloat16 b) noexcept { return static_cast<NearestFloat>(a) == static_cast<NearestFloat>(b); } |
195 | friend inline bool operator!=(qfloat16 a, qfloat16 b) noexcept { return static_cast<NearestFloat>(a) != static_cast<NearestFloat>(b); } |
196 | |
197 | #define QF16_MAKE_BOOL_OP_FP(FP, OP) \ |
198 | friend inline bool operator OP(qfloat16 lhs, FP rhs) noexcept { return static_cast<FP>(lhs) OP rhs; } \ |
199 | friend inline bool operator OP(FP lhs, qfloat16 rhs) noexcept { return lhs OP static_cast<FP>(rhs); } |
200 | #define QF16_MAKE_BOOL_OP(FP) \ |
201 | QF16_MAKE_BOOL_OP_FP(FP, <) \ |
202 | QF16_MAKE_BOOL_OP_FP(FP, >) \ |
203 | QF16_MAKE_BOOL_OP_FP(FP, >=) \ |
204 | QF16_MAKE_BOOL_OP_FP(FP, <=) \ |
205 | QF16_MAKE_BOOL_OP_FP(FP, ==) \ |
206 | QF16_MAKE_BOOL_OP_FP(FP, !=) |
207 | |
208 | QF16_MAKE_BOOL_OP(long double) |
209 | QF16_MAKE_BOOL_OP(double) |
210 | QF16_MAKE_BOOL_OP(float) |
211 | #undef QF16_MAKE_BOOL_OP |
212 | #undef QF16_MAKE_BOOL_OP_FP |
213 | |
214 | #define QF16_MAKE_BOOL_OP_INT(OP) \ |
215 | friend inline bool operator OP(qfloat16 a, int b) noexcept { return static_cast<NearestFloat>(a) OP static_cast<NearestFloat>(b); } \ |
216 | friend inline bool operator OP(int a, qfloat16 b) noexcept { return static_cast<NearestFloat>(a) OP static_cast<NearestFloat>(b); } |
217 | |
218 | QF16_MAKE_BOOL_OP_INT(>) |
219 | QF16_MAKE_BOOL_OP_INT(<) |
220 | QF16_MAKE_BOOL_OP_INT(>=) |
221 | QF16_MAKE_BOOL_OP_INT(<=) |
222 | QF16_MAKE_BOOL_OP_INT(==) |
223 | QF16_MAKE_BOOL_OP_INT(!=) |
224 | #undef QF16_MAKE_BOOL_OP_INT |
225 | |
226 | QT_WARNING_POP |
227 | |
228 | #ifndef QT_NO_DATASTREAM |
229 | friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &ds, qfloat16 f); |
230 | friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &ds, qfloat16 &f); |
231 | #endif |
232 | friend Q_CORE_EXPORT QTextStream &operator<<(QTextStream &ts, qfloat16 f); |
233 | friend Q_CORE_EXPORT QTextStream &operator>>(QTextStream &ts, qfloat16 &f); |
234 | }; |
235 | |
236 | Q_DECLARE_TYPEINFO(qfloat16, Q_PRIMITIVE_TYPE); |
237 | |
238 | Q_CORE_EXPORT void qFloatToFloat16(qfloat16 *, const float *, qsizetype length) noexcept; |
239 | Q_CORE_EXPORT void qFloatFromFloat16(float *, const qfloat16 *, qsizetype length) noexcept; |
240 | |
241 | // Complement qnumeric.h: |
242 | [[nodiscard]] inline bool qIsInf(qfloat16 f) noexcept { return f.isInf(); } |
243 | [[nodiscard]] inline bool qIsNaN(qfloat16 f) noexcept { return f.isNaN(); } |
244 | [[nodiscard]] inline bool qIsFinite(qfloat16 f) noexcept { return f.isFinite(); } |
245 | [[nodiscard]] inline int qFpClassify(qfloat16 f) noexcept { return f.fpClassify(); } |
246 | // [[nodiscard]] quint32 qFloatDistance(qfloat16 a, qfloat16 b); |
247 | |
248 | [[nodiscard]] inline qfloat16 qSqrt(qfloat16 f) |
249 | { |
250 | #if defined(__cpp_lib_extended_float) && defined(__STDCPP_FLOAT16_T__) && 0 |
251 | // https://wg21.link/p1467 - disabled until tested |
252 | using namespace std; |
253 | return sqrt(f); |
254 | #elif QFLOAT16_IS_NATIVE && defined(__HAVE_FLOAT16) && __HAVE_FLOAT16 |
255 | // This C library (glibc) has sqrtf16(). |
256 | return sqrtf16(f); |
257 | #else |
258 | bool mathUpdatesErrno = true; |
259 | # if defined(__NO_MATH_ERRNO__) || defined(_M_FP_FAST) |
260 | mathUpdatesErrno = false; |
261 | # elif defined(math_errhandling) |
262 | mathUpdatesErrno = (math_errhandling & MATH_ERRNO); |
263 | # endif |
264 | |
265 | // We don't need to set errno to EDOM if (f >= 0 && f != -0 && !isnan(f)) |
266 | // (or if we don't care about errno in the first place). We can merge the |
267 | // NaN check with by negating and inverting: !(0 > f), and leaving zero to |
268 | // sqrtf(). |
269 | if (!mathUpdatesErrno || !(0 > f)) { |
270 | # if defined(__AVX512FP16__) |
271 | __m128h v = _mm_set_sh(f); |
272 | v = _mm_sqrt_sh(v, v); |
273 | return _mm_cvtsh_h(v); |
274 | # endif |
275 | } |
276 | |
277 | // WG14's N2601 does not provide a way to tell which types an |
278 | // implementation supports, so we assume it doesn't and fall back to FP32 |
279 | float f32 = float(f); |
280 | f32 = sqrtf(x: f32); |
281 | return qfloat16::NearestFloat(f32); |
282 | #endif |
283 | } |
284 | |
285 | // The remainder of these utility functions complement qglobal.h |
286 | [[nodiscard]] inline int qRound(qfloat16 d) noexcept |
287 | { return qRound(f: static_cast<float>(d)); } |
288 | |
289 | [[nodiscard]] inline qint64 qRound64(qfloat16 d) noexcept |
290 | { return qRound64(f: static_cast<float>(d)); } |
291 | |
292 | [[nodiscard]] inline bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept |
293 | { |
294 | qfloat16::NearestFloat f1 = static_cast<qfloat16::NearestFloat>(p1); |
295 | qfloat16::NearestFloat f2 = static_cast<qfloat16::NearestFloat>(p2); |
296 | // The significand precision for IEEE754 half precision is |
297 | // 11 bits (10 explicitly stored), or approximately 3 decimal |
298 | // digits. In selecting the fuzzy comparison factor of 102.5f |
299 | // (that is, (2^10+1)/10) below, we effectively select a |
300 | // window of about 1 (least significant) decimal digit about |
301 | // which the two operands can vary and still return true. |
302 | return (qAbs(t: f1 - f2) * 102.5f <= qMin(a: qAbs(t: f1), b: qAbs(t: f2))); |
303 | } |
304 | |
305 | /*! |
306 | \internal |
307 | */ |
308 | [[nodiscard]] inline bool qFuzzyIsNull(qfloat16 f) noexcept |
309 | { |
310 | return qAbs(t: f) < 0.00976f; // 1/102.5 to 3 significant digits; see qFuzzyCompare() |
311 | } |
312 | |
313 | [[nodiscard]] inline bool qIsNull(qfloat16 f) noexcept |
314 | { |
315 | return (f.b16 & static_cast<quint16>(0x7fff)) == 0; |
316 | } |
317 | |
318 | inline int qIntCast(qfloat16 f) noexcept |
319 | { return int(static_cast<qfloat16::NearestFloat>(f)); } |
320 | |
321 | #if !defined(Q_QDOC) && !QFLOAT16_IS_NATIVE |
322 | QT_WARNING_PUSH |
323 | QT_WARNING_DISABLE_CLANG("-Wc99-extensions" ) |
324 | QT_WARNING_DISABLE_GCC("-Wold-style-cast" ) |
325 | inline qfloat16::qfloat16(float f) noexcept |
326 | { |
327 | #if defined(QT_COMPILER_SUPPORTS_F16C) && defined(__F16C__) |
328 | __m128 packsingle = _mm_set_ss(f); |
329 | __m128i packhalf = _mm_cvtps_ph(packsingle, 0); |
330 | b16 = _mm_extract_epi16(packhalf, 0); |
331 | #elif defined (__ARM_FP16_FORMAT_IEEE) |
332 | __fp16 f16 = __fp16(f); |
333 | memcpy(&b16, &f16, sizeof(quint16)); |
334 | #else |
335 | quint32 u; |
336 | memcpy(dest: &u, src: &f, n: sizeof(quint32)); |
337 | const quint32 signAndExp = u >> 23; |
338 | const quint16 base = basetable[signAndExp]; |
339 | const quint16 shift = shifttable[signAndExp]; |
340 | const quint32 round = roundtable[signAndExp]; |
341 | quint32 mantissa = (u & 0x007fffff); |
342 | if ((signAndExp & 0xff) == 0xff) { |
343 | if (mantissa) // keep nan from truncating to inf |
344 | mantissa = qMax(a: 1U << shift, b: mantissa); |
345 | } else { |
346 | // Round half to even. First round up by adding one in the most |
347 | // significant bit we'll be discarding: |
348 | mantissa += round; |
349 | // If the last bit we'll be keeping is now set, but all later bits are |
350 | // clear, we were at half and shouldn't have rounded up; decrement will |
351 | // clear this last kept bit. Any later set bit hides the decrement. |
352 | if (mantissa & (1 << shift)) |
353 | --mantissa; |
354 | } |
355 | |
356 | // We use add as the mantissa may overflow causing |
357 | // the exp part to shift exactly one value. |
358 | b16 = quint16(base + (mantissa >> shift)); |
359 | #endif |
360 | } |
361 | QT_WARNING_POP |
362 | |
363 | inline qfloat16::operator float() const noexcept |
364 | { |
365 | #if defined(QT_COMPILER_SUPPORTS_F16C) && defined(__F16C__) |
366 | __m128i packhalf = _mm_cvtsi32_si128(b16); |
367 | __m128 packsingle = _mm_cvtph_ps(packhalf); |
368 | return _mm_cvtss_f32(packsingle); |
369 | #elif defined (__ARM_FP16_FORMAT_IEEE) |
370 | __fp16 f16; |
371 | memcpy(&f16, &b16, sizeof(quint16)); |
372 | return float(f16); |
373 | #else |
374 | quint32 u = mantissatable[offsettable[b16 >> 10] + (b16 & 0x3ff)] |
375 | + exponenttable[b16 >> 10]; |
376 | float f; |
377 | memcpy(dest: &f, src: &u, n: sizeof(quint32)); |
378 | return f; |
379 | #endif |
380 | } |
381 | #endif // Q_QDOC and non-native |
382 | |
383 | /* |
384 | qHypot compatibility; see ../kernel/qmath.h |
385 | */ |
386 | namespace QtPrivate { |
387 | template <> struct QHypotType<qfloat16, qfloat16> |
388 | { |
389 | using type = qfloat16; |
390 | }; |
391 | template <typename R> struct QHypotType<R, qfloat16> |
392 | { |
393 | using type = std::conditional_t<std::is_floating_point_v<R>, R, double>; |
394 | }; |
395 | template <typename R> struct QHypotType<qfloat16, R> : QHypotType<R, qfloat16> |
396 | { |
397 | }; |
398 | } |
399 | |
400 | // Avoid passing qfloat16 to std::hypot(), while ensuring return types |
401 | // consistent with the above: |
402 | inline auto qHypot(qfloat16 x, qfloat16 y) |
403 | { |
404 | #if defined(QT_COMPILER_SUPPORTS_F16C) && defined(__F16C__) || QFLOAT16_IS_NATIVE |
405 | return QtPrivate::QHypotHelper<qfloat16>(x).add(y).result(); |
406 | #else |
407 | return qfloat16(qHypot(x: float(x), y: float(y))); |
408 | #endif |
409 | } |
410 | |
411 | // in ../kernel/qmath.h |
412 | template<typename F, typename ...Fs> auto qHypot(F first, Fs... rest); |
413 | |
414 | template <typename T> typename QtPrivate::QHypotType<T, qfloat16>::type |
415 | qHypot(T x, qfloat16 y) |
416 | { |
417 | if constexpr (std::is_floating_point_v<T>) |
418 | return qHypot(x, float(y)); |
419 | else |
420 | return qHypot(x: qfloat16(x), y); |
421 | } |
422 | template <typename T> auto qHypot(qfloat16 x, T y) |
423 | { |
424 | return qHypot(y, x); |
425 | } |
426 | |
427 | #if defined(__cpp_lib_hypot) && __cpp_lib_hypot >= 201603L // Expected to be true |
428 | // If any are not qfloat16, convert each qfloat16 to float: |
429 | /* (The following splits the some-but-not-all-qfloat16 cases up, using |
430 | (X|Y|Z)&~(X&Y&Z) = X ? ~(Y&Z) : Y|Z = X&~(Y&Z) | ~X&Y | ~X&~Y&Z, |
431 | into non-overlapping cases, to avoid ambiguity.) */ |
432 | template <typename Ty, typename Tz, |
433 | typename std::enable_if< |
434 | // Ty, Tz aren't both qfloat16: |
435 | !(std::is_same_v<qfloat16, Ty> && std::is_same_v<qfloat16, Tz>), int>::type = 0> |
436 | auto qHypot(qfloat16 x, Ty y, Tz z) { return qHypot(qfloat16::NearestFloat(x), y, z); } |
437 | template <typename Tx, typename Tz, |
438 | typename std::enable_if< |
439 | // Tx isn't qfloat16: |
440 | !std::is_same_v<qfloat16, Tx>, int>::type = 0> |
441 | auto qHypot(Tx x, qfloat16 y, Tz z) { return qHypot(x, qfloat16::NearestFloat(y), z); } |
442 | template <typename Tx, typename Ty, |
443 | typename std::enable_if< |
444 | // Neither Tx nor Ty is qfloat16: |
445 | !std::is_same_v<qfloat16, Tx> && !std::is_same_v<qfloat16, Ty>, int>::type = 0> |
446 | auto qHypot(Tx x, Ty y, qfloat16 z) { return qHypot(x, y, qfloat16::NearestFloat(z)); } |
447 | |
448 | // If all are qfloat16, stay with qfloat16 (albeit via float, if no native support): |
449 | inline auto qHypot(qfloat16 x, qfloat16 y, qfloat16 z) |
450 | { |
451 | #if (defined(QT_COMPILER_SUPPORTS_F16C) && defined(__F16C__)) || QFLOAT16_IS_NATIVE |
452 | return QtPrivate::QHypotHelper<qfloat16>(x).add(y).add(z).result(); |
453 | #else |
454 | return qfloat16(qHypot(x: float(x), y: float(y), z: float(z))); |
455 | #endif |
456 | } |
457 | #endif // 3-arg std::hypot() is available |
458 | |
459 | QT_END_NAMESPACE |
460 | |
461 | namespace std { |
462 | template<> |
463 | class numeric_limits<QT_PREPEND_NAMESPACE(qfloat16)> : public numeric_limits<float> |
464 | { |
465 | public: |
466 | /* |
467 | Treat quint16 b16 as if it were: |
468 | uint S: 1; // b16 >> 15 (sign); can be set for zero |
469 | uint E: 5; // (b16 >> 10) & 0x1f (offset exponent) |
470 | uint M: 10; // b16 & 0x3ff (adjusted mantissa) |
471 | |
472 | for E == 0: magnitude is M / 2.^{24} |
473 | for 0 < E < 31: magnitude is (1. + M / 2.^{10}) * 2.^{E - 15) |
474 | for E == 31: not finite |
475 | */ |
476 | static constexpr int digits = 11; |
477 | static constexpr int min_exponent = -13; |
478 | static constexpr int max_exponent = 16; |
479 | |
480 | static constexpr int digits10 = 3; |
481 | static constexpr int max_digits10 = 5; |
482 | static constexpr int min_exponent10 = -4; |
483 | static constexpr int max_exponent10 = 4; |
484 | |
485 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) epsilon() |
486 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_epsilon(); } |
487 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) (min)() |
488 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_min(); } |
489 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) denorm_min() |
490 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_denorm_min(); } |
491 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) (max)() |
492 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_max(); } |
493 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) lowest() |
494 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_lowest(); } |
495 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) infinity() |
496 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_infinity(); } |
497 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) quiet_NaN() |
498 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_quiet_NaN(); } |
499 | #if QT_CONFIG(signaling_nan) |
500 | static constexpr QT_PREPEND_NAMESPACE(qfloat16) signaling_NaN() |
501 | { return QT_PREPEND_NAMESPACE(qfloat16)::_limit_signaling_NaN(); } |
502 | #else |
503 | static constexpr bool has_signaling_NaN = false; |
504 | #endif |
505 | }; |
506 | |
507 | template<> class numeric_limits<const QT_PREPEND_NAMESPACE(qfloat16)> |
508 | : public numeric_limits<QT_PREPEND_NAMESPACE(qfloat16)> {}; |
509 | template<> class numeric_limits<volatile QT_PREPEND_NAMESPACE(qfloat16)> |
510 | : public numeric_limits<QT_PREPEND_NAMESPACE(qfloat16)> {}; |
511 | template<> class numeric_limits<const volatile QT_PREPEND_NAMESPACE(qfloat16)> |
512 | : public numeric_limits<QT_PREPEND_NAMESPACE(qfloat16)> {}; |
513 | |
514 | // Adding overloads to std isn't allowed, so we can't extend this to support |
515 | // for fpclassify(), isnormal() &c. (which, furthermore, are macros on MinGW). |
516 | } // namespace std |
517 | |
518 | #endif // QFLOAT16_H |
519 | |