Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfloat16.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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#include "qfloat16.h"
6#include "private/qsimd_p.h"
7#include <cmath> // for fpclassify()'s return values
8
9#include <QtCore/qdatastream.h>
10#include <QtCore/qmetatype.h>
11#include <QtCore/qtextstream.h>
12
14
15#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
17{
18 return QMetaType::Float16;
19}
20#endif
21
22/*!
23 \class qfloat16
24 \keyword 16-bit Floating Point Support
25 \ingroup funclists
26 \inmodule QtCore
27 \inheaderfile QFloat16
28 \brief Provides 16-bit floating point support.
29
30 \compares partial
31 \compareswith partial float double {long double} qint8 quint8 qint16 quint16 \
32 qint32 quint32 long {unsigned long} qint64 quint64
33 \endcompareswith
34 \compareswith partial qint128 quint128
35 Comparison with 128-bit integral types is only supported if Qt provides
36 these types.
37 \endcompareswith
38
39 The \c qfloat16 class provides support for half-precision (16-bit) floating
40 point data. It is fully compliant with IEEE 754 as a storage type. This
41 implies that any arithmetic operation on a \c qfloat16 instance results in
42 the value first being converted to a \c float. This conversion to and from
43 \c float is performed by hardware when possible, but on processors that do
44 not natively support half-precision, the conversion is performed through a
45 sequence of lookup table operations.
46
47 \c qfloat16 should be treated as if it were a POD (plain old data) type.
48 Consequently, none of the supported operations need any elaboration beyond
49 stating that it supports all arithmetic operators incident to floating point
50 types.
51
52 \note On x86 and x86-64 that to get hardware accelerated conversions you must
53 compile with F16C or AVX2 enabled, or use qFloatToFloat16() and qFloatFromFloat16()
54 which will detect F16C at runtime.
55
56 \since 5.9
57*/
58
59/*!
60 \fn qfloat16::qfloat16(Qt::Initialization)
61 \since 6.1
62
63 Constructs a qfloat16 without initializing the value.
64*/
65
66/*!
67 \fn bool qIsInf(qfloat16 f)
68 \relates qfloat16
69 \overload qIsInf(float)
70
71 Returns true if the \c qfloat16 \a {f} is equivalent to infinity.
72*/
73
74/*!
75 \fn bool qIsNaN(qfloat16 f)
76 \relates qfloat16
77 \overload qIsNaN(float)
78
79 Returns true if the \c qfloat16 \a {f} is not a number (NaN).
80*/
81
82/*!
83 \fn bool qIsFinite(qfloat16 f)
84 \relates qfloat16
85 \overload qIsFinite(float)
86
87 Returns true if the \c qfloat16 \a {f} is a finite number.
88*/
89
90/*!
91 \internal
92 \since 5.14
93 \fn bool qfloat16::isInf() const noexcept
94
95 Tests whether this \c qfloat16 value is an infinity.
96*/
97
98/*!
99 \internal
100 \since 5.14
101 \fn bool qfloat16::isNaN() const noexcept
102
103 Tests whether this \c qfloat16 value is "not a number".
104*/
105
106/*!
107 \since 5.14
108 \fn bool qfloat16::isNormal() const noexcept
109
110 Returns \c true if this \c qfloat16 value is finite and in normal form.
111
112 \sa qFpClassify()
113*/
114
115/*!
116 \internal
117 \since 5.14
118 \fn bool qfloat16::isFinite() const noexcept
119
120 Tests whether this \c qfloat16 value is finite.
121*/
122
123/*!
124 \since 5.15
125 \fn qfloat16 qfloat16::copySign(qfloat16 sign) const noexcept
126 \obsolete [6.11] Use the copysign() friend function instead.
127*/
128
129/*!
130 \since 6.11
131 \fn bool qfloat16::copysign(qfloat16 x, qfloat16 sign)
132
133 Returns a qfloat16 with the sign of \a sign but the rest of its value taken
134 from \a{x}. Serves as qfloat16's equivalent of std::copysign().
135
136 \sa signbit()
137*/
138
139/*!
140 \since 6.11
141 \fn bool qfloat16::signbit(qfloat16 x)
142
143 Returns true if qfloat16 \a x is negative, false otherwise. Note this
144 function returns true for negative zero, negative infinity, and negative
145 NaN values.
146
147 \sa copysign()
148*/
149
150/*!
151 \fn int qFpClassify(qfloat16 val)
152 \relates qfloat16
153 \since 5.14
154 \overload qFpClassify(float)
155
156 Returns the floating-point class of \a val.
157*/
158
159/*!
160 \internal
161 \since 5.14
162 Implements qFpClassify() for qfloat16.
163*/
164int qfloat16::fpClassify() const noexcept
165{
166 return isInf() ? FP_INFINITE : isNaN() ? FP_NAN
167 : !(b16 & 0x7fff) ? FP_ZERO : isNormal() ? FP_NORMAL : FP_SUBNORMAL;
168}
169
170/*! \fn int qRound(qfloat16 value)
171 \relates qfloat16
172 \overload qRound(float)
173
174 Rounds \a value to the nearest integer.
175*/
176
177/*! \fn qint64 qRound64(qfloat16 value)
178 \relates qfloat16
179 \overload qRound64(float)
180
181 Rounds \a value to the nearest 64-bit integer.
182*/
183
184/*! \fn bool qFuzzyCompare(qfloat16 p1, qfloat16 p2)
185 \relates qfloat16
186 \overload qFuzzyCompare(float, float)
187
188 Compares the floating point value \a p1 and \a p2 and
189 returns \c true if they are considered equal, otherwise \c false.
190
191 The two numbers are compared in a relative way, where the
192 exactness is stronger the smaller the numbers are.
193 */
194
195#if QT_COMPILER_SUPPORTS_HERE(F16C)
196static inline bool hasFastF16()
197{
198 // qsimd.cpp:detectProcessorFeatures() turns off this feature if AVX
199 // state-saving is not enabled by the OS
200 return qCpuHasFeature(F16C);
201}
202
203#if QT_COMPILER_SUPPORTS_HERE(AVX512VL) && QT_COMPILER_SUPPORTS_HERE(AVX512BW)
204static bool hasFastF16Avx256()
205{
206 // 256-bit AVX512 don't have a performance penalty (see qstring.cpp for more info)
207 return qCpuHasFeature(ArchSkylakeAvx512);
208}
209
210static QT_FUNCTION_TARGET(ARCH_SKYLAKE_AVX512)
211void qFloatToFloat16_tail_avx256(quint16 *out, const float *in, qsizetype len) noexcept
212{
213 __mmask16 mask = _bzhi_u32(-1, len);
214 __m256 f32 = _mm256_maskz_loadu_ps(mask, in );
215 __m128i f16 = _mm256_maskz_cvtps_ph(mask, f32, _MM_FROUND_TO_NEAREST_INT);
216 _mm_mask_storeu_epi16(out, mask, f16);
217};
218
219static QT_FUNCTION_TARGET(ARCH_SKYLAKE_AVX512)
220void qFloatFromFloat16_tail_avx256(float *out, const quint16 *in, qsizetype len) noexcept
221{
222 __mmask16 mask = _bzhi_u32(-1, len);
223 __m128i f16 = _mm_maskz_loadu_epi16(mask, in);
224 __m256 f32 = _mm256_cvtph_ps(f16);
225 _mm256_mask_storeu_ps(out, mask, f32);
226};
227#endif
228
229QT_FUNCTION_TARGET(F16C)
230static void qFloatToFloat16_fast(quint16 *out, const float *in, qsizetype len) noexcept
231{
232 constexpr qsizetype Step = sizeof(__m256i) / sizeof(float);
233 constexpr qsizetype HalfStep = sizeof(__m128i) / sizeof(float);
234 qsizetype i = 0;
235
236 if (len >= Step) {
237 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
238 __m256 f32 = _mm256_loadu_ps(in + offset);
239 __m128i f16 = _mm256_cvtps_ph(f32, _MM_FROUND_TO_NEAREST_INT);
240 _mm_storeu_si128(reinterpret_cast<__m128i *>(out + offset), f16);
241 };
242
243 // main loop: convert Step (8) floats per iteration
244 for ( ; i + Step < len; i += Step)
245 convertOneChunk(i);
246
247 // epilogue: convert the last chunk, possibly overlapping with the last
248 // iteration of the loop
249 return convertOneChunk(len - Step);
250 }
251
252#if QT_COMPILER_SUPPORTS_HERE(AVX512VL) && QT_COMPILER_SUPPORTS_HERE(AVX512BW)
253 if (hasFastF16Avx256())
254 return qFloatToFloat16_tail_avx256(out, in, len);
255#endif
256
257 if (len >= HalfStep) {
258 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
259 __m128 f32 = _mm_loadu_ps(in + offset);
260 __m128i f16 = _mm_cvtps_ph(f32, _MM_FROUND_TO_NEAREST_INT);
261 _mm_storel_epi64(reinterpret_cast<__m128i *>(out + offset), f16);
262 };
263
264 // two conversions, possibly overlapping
265 convertOneChunk(0);
266 return convertOneChunk(len - HalfStep);
267 }
268
269 // Inlining "qfloat16::qfloat16(float f)":
270 for ( ; i < len; ++i)
271 out[i] = _mm_extract_epi16(_mm_cvtps_ph(_mm_set_ss(in[i]), 0), 0);
272}
273
274QT_FUNCTION_TARGET(F16C)
275static void qFloatFromFloat16_fast(float *out, const quint16 *in, qsizetype len) noexcept
276{
277 constexpr qsizetype Step = sizeof(__m256i) / sizeof(float);
278 constexpr qsizetype HalfStep = sizeof(__m128i) / sizeof(float);
279 qsizetype i = 0;
280
281 if (len >= Step) {
282 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
283 __m128i f16 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + offset));
284 __m256 f32 = _mm256_cvtph_ps(f16);
285 _mm256_storeu_ps(out + offset, f32);
286 };
287
288 // main loop: convert Step (8) floats per iteration
289 for ( ; i + Step < len; i += Step)
290 convertOneChunk(i);
291
292 // epilogue: convert the last chunk, possibly overlapping with the last
293 // iteration of the loop
294 return convertOneChunk(len - Step);
295 }
296
297#if QT_COMPILER_SUPPORTS_HERE(AVX512VL) && QT_COMPILER_SUPPORTS_HERE(AVX512BW)
298 if (hasFastF16Avx256())
299 return qFloatFromFloat16_tail_avx256(out, in, len);
300#endif
301
302 if (len >= HalfStep) {
303 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
304 __m128i f16 = _mm_loadl_epi64(reinterpret_cast<const __m128i *>(in + offset));
305 __m128 f32 = _mm_cvtph_ps(f16);
306 _mm_storeu_ps(out + offset, f32);
307 };
308
309 // two conversions, possibly overlapping
310 convertOneChunk(0);
311 return convertOneChunk(len - HalfStep);
312 }
313
314 // Inlining "qfloat16::operator float()":
315 for ( ; i < len; ++i)
316 out[i] = _mm_cvtss_f32(_mm_cvtph_ps(_mm_cvtsi32_si128(in[i])));
317}
318
319#elif defined(__ARM_FP16_FORMAT_IEEE) && defined(__ARM_NEON__) && (__ARM_FP & 2)
320static inline bool hasFastF16()
321{
322 return true;
323}
324
325static void qFloatToFloat16_fast(quint16 *out, const float *in, qsizetype len) noexcept
326{
327 __fp16 *out_f16 = reinterpret_cast<__fp16 *>(out);
328 qsizetype i = 0;
329 for (; i < len - 3; i += 4)
330 vst1_f16(out_f16 + i, vcvt_f16_f32(vld1q_f32(in + i)));
331 SIMD_EPILOGUE(i, len, 3)
332 out_f16[i] = __fp16(in[i]);
333}
334
335static void qFloatFromFloat16_fast(float *out, const quint16 *in, qsizetype len) noexcept
336{
337 const __fp16 *in_f16 = reinterpret_cast<const __fp16 *>(in);
338 qsizetype i = 0;
339 for (; i < len - 3; i += 4)
340 vst1q_f32(out + i, vcvt_f32_f16(vld1_f16(in_f16 + i)));
341 SIMD_EPILOGUE(i, len, 3)
342 out[i] = float(in_f16[i]);
343}
344#else
345static inline bool hasFastF16()
346{
347 return false;
348}
349
350static void qFloatToFloat16_fast(quint16 *, const float *, qsizetype) noexcept
351{
352 Q_UNREACHABLE();
353}
354
355static void qFloatFromFloat16_fast(float *, const quint16 *, qsizetype) noexcept
356{
357 Q_UNREACHABLE();
358}
359#endif
360/*!
361 \since 5.11
362 \relates qfloat16
363
364 Converts \a len floats from \a in to qfloat16 and stores them in \a out.
365 Both \a in and \a out must have \a len allocated entries.
366
367 This function is faster than converting values one by one, and will do runtime
368 F16C detection on x86 and x86-64 hardware.
369*/
370Q_CORE_EXPORT void qFloatToFloat16(qfloat16 *out, const float *in, qsizetype len) noexcept
371{
372 if (hasFastF16())
373 return qFloatToFloat16_fast(reinterpret_cast<quint16 *>(out), in, len);
374
375 for (qsizetype i = 0; i < len; ++i)
376 out[i] = qfloat16(in[i]);
377}
378
379/*!
380 \since 5.11
381 \relates qfloat16
382
383 Converts \a len qfloat16 from \a in to floats and stores them in \a out.
384 Both \a in and \a out must have \a len allocated entries.
385
386 This function is faster than converting values one by one, and will do runtime
387 F16C detection on x86 and x86-64 hardware.
388*/
389Q_CORE_EXPORT void qFloatFromFloat16(float *out, const qfloat16 *in, qsizetype len) noexcept
390{
391 if (hasFastF16())
392 return qFloatFromFloat16_fast(out, reinterpret_cast<const quint16 *>(in), len);
393
394 for (qsizetype i = 0; i < len; ++i)
395 out[i] = float(in[i]);
396}
397
398/*!
399 \fn size_t qfloat16::qHash(qfloat16 key, size_t seed)
400 \since 6.5.3
401 \qhash{qfloat16}
402
403 \note In Qt versions before 6.5, this operation was provided by the
404 qHash(float) overload. In Qt versions 6.5.0 to 6.5.2, this functionality
405 was broken in various ways. In Qt versions 6.5.3 and 6.6 onwards, this
406 overload restores the Qt 6.4 behavior.
407*/
408
409#ifndef QT_NO_DATASTREAM
410/*!
411 \fn qfloat16::operator<<(QDataStream &ds, qfloat16 f)
412 \relates QDataStream
413 \since 5.9
414
415 Writes a floating point number, \a f, to the stream \a ds using
416 the standard IEEE 754 format. Returns a reference to the stream.
417
418 \note In Qt versions prior to 6.3, this was a member function on
419 QDataStream.
420*/
421QDataStream &operator<<(QDataStream &ds, qfloat16 f)
422{
423 return ds << f.b16;
424}
425
426/*!
427 \fn qfloat16::operator>>(QDataStream &ds, qfloat16 &f)
428 \relates QDataStream
429 \since 5.9
430
431 Reads a floating point number from the stream \a ds into \a f,
432 using the standard IEEE 754 format. Returns a reference to the
433 stream.
434
435 \note In Qt versions prior to 6.3, this was a member function on
436 QDataStream.
437*/
438QDataStream &operator>>(QDataStream &ds, qfloat16 &f)
439{
440 return ds >> f.b16;
441}
442#endif
443
444QTextStream &operator>>(QTextStream &ts, qfloat16 &f16)
445{
446 float f;
447 ts >> f;
448 f16 = qfloat16(f);
449 return ts;
450}
451
452QTextStream &operator<<(QTextStream &ts, qfloat16 f)
453{
454 return ts << float(f);
455}
456
457QT_END_NAMESPACE
458
\keyword 16-bit Floating Point Support\inmodule QtCore \inheaderfile QFloat16
Definition qfloat16.h:56
Q_CORE_EXPORT int fpClassify() const noexcept
Definition qfloat16.cpp:164
QTextStream & operator>>(QTextStream &ts, qfloat16 &f16)
Definition qfloat16.cpp:444
static bool hasFastF16()
Definition qfloat16.cpp:345
static void qFloatToFloat16_fast(quint16 *, const float *, qsizetype) noexcept
Definition qfloat16.cpp:350
static void qFloatFromFloat16_fast(float *, const quint16 *, qsizetype) noexcept
Definition qfloat16.cpp:355
QDataStream & operator>>(QDataStream &ds, qfloat16 &f)
Definition qfloat16.cpp:438
Q_CORE_EXPORT void qFloatFromFloat16(float *, const qfloat16 *, qsizetype length) noexcept
Q_CORE_EXPORT void qFloatToFloat16(qfloat16 *, const float *, qsizetype length) noexcept