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
127 Returns a qfloat16 with the sign of \a sign but the rest of its value taken
128 from this qfloat16. Serves as qfloat16's equivalent of std::copysign().
129*/
130
131/*!
132 \fn int qFpClassify(qfloat16 val)
133 \relates qfloat16
134 \since 5.14
135 \overload qFpClassify(float)
136
137 Returns the floating-point class of \a val.
138*/
139
140/*!
141 \internal
142 \since 5.14
143 Implements qFpClassify() for qfloat16.
144*/
145int qfloat16::fpClassify() const noexcept
146{
147 return isInf() ? FP_INFINITE : isNaN() ? FP_NAN
148 : !(b16 & 0x7fff) ? FP_ZERO : isNormal() ? FP_NORMAL : FP_SUBNORMAL;
149}
150
151/*! \fn int qRound(qfloat16 value)
152 \relates qfloat16
153 \overload qRound(float)
154
155 Rounds \a value to the nearest integer.
156*/
157
158/*! \fn qint64 qRound64(qfloat16 value)
159 \relates qfloat16
160 \overload qRound64(float)
161
162 Rounds \a value to the nearest 64-bit integer.
163*/
164
165/*! \fn bool qFuzzyCompare(qfloat16 p1, qfloat16 p2)
166 \relates qfloat16
167 \overload qFuzzyCompare(float, float)
168
169 Compares the floating point value \a p1 and \a p2 and
170 returns \c true if they are considered equal, otherwise \c false.
171
172 The two numbers are compared in a relative way, where the
173 exactness is stronger the smaller the numbers are.
174 */
175
176#if QT_COMPILER_SUPPORTS_HERE(F16C)
177static inline bool hasFastF16()
178{
179 // qsimd.cpp:detectProcessorFeatures() turns off this feature if AVX
180 // state-saving is not enabled by the OS
181 return qCpuHasFeature(F16C);
182}
183
184#if QT_COMPILER_SUPPORTS_HERE(AVX512VL) && QT_COMPILER_SUPPORTS_HERE(AVX512BW)
185static bool hasFastF16Avx256()
186{
187 // 256-bit AVX512 don't have a performance penalty (see qstring.cpp for more info)
188 return qCpuHasFeature(ArchSkylakeAvx512);
189}
190
191static QT_FUNCTION_TARGET(ARCH_SKYLAKE_AVX512)
192void qFloatToFloat16_tail_avx256(quint16 *out, const float *in, qsizetype len) noexcept
193{
194 __mmask16 mask = _bzhi_u32(-1, len);
195 __m256 f32 = _mm256_maskz_loadu_ps(mask, in );
196 __m128i f16 = _mm256_maskz_cvtps_ph(mask, f32, _MM_FROUND_TO_NEAREST_INT);
197 _mm_mask_storeu_epi16(out, mask, f16);
198};
199
200static QT_FUNCTION_TARGET(ARCH_SKYLAKE_AVX512)
201void qFloatFromFloat16_tail_avx256(float *out, const quint16 *in, qsizetype len) noexcept
202{
203 __mmask16 mask = _bzhi_u32(-1, len);
204 __m128i f16 = _mm_maskz_loadu_epi16(mask, in);
205 __m256 f32 = _mm256_cvtph_ps(f16);
206 _mm256_mask_storeu_ps(out, mask, f32);
207};
208#endif
209
210QT_FUNCTION_TARGET(F16C)
211static void qFloatToFloat16_fast(quint16 *out, const float *in, qsizetype len) noexcept
212{
213 constexpr qsizetype Step = sizeof(__m256i) / sizeof(float);
214 constexpr qsizetype HalfStep = sizeof(__m128i) / sizeof(float);
215 qsizetype i = 0;
216
217 if (len >= Step) {
218 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
219 __m256 f32 = _mm256_loadu_ps(in + offset);
220 __m128i f16 = _mm256_cvtps_ph(f32, _MM_FROUND_TO_NEAREST_INT);
221 _mm_storeu_si128(reinterpret_cast<__m128i *>(out + offset), f16);
222 };
223
224 // main loop: convert Step (8) floats per iteration
225 for ( ; i + Step < len; i += Step)
226 convertOneChunk(i);
227
228 // epilogue: convert the last chunk, possibly overlapping with the last
229 // iteration of the loop
230 return convertOneChunk(len - Step);
231 }
232
233#if QT_COMPILER_SUPPORTS_HERE(AVX512VL) && QT_COMPILER_SUPPORTS_HERE(AVX512BW)
234 if (hasFastF16Avx256())
235 return qFloatToFloat16_tail_avx256(out, in, len);
236#endif
237
238 if (len >= HalfStep) {
239 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
240 __m128 f32 = _mm_loadu_ps(in + offset);
241 __m128i f16 = _mm_cvtps_ph(f32, _MM_FROUND_TO_NEAREST_INT);
242 _mm_storel_epi64(reinterpret_cast<__m128i *>(out + offset), f16);
243 };
244
245 // two conversions, possibly overlapping
246 convertOneChunk(0);
247 return convertOneChunk(len - HalfStep);
248 }
249
250 // Inlining "qfloat16::qfloat16(float f)":
251 for ( ; i < len; ++i)
252 out[i] = _mm_extract_epi16(_mm_cvtps_ph(_mm_set_ss(in[i]), 0), 0);
253}
254
255QT_FUNCTION_TARGET(F16C)
256static void qFloatFromFloat16_fast(float *out, const quint16 *in, qsizetype len) noexcept
257{
258 constexpr qsizetype Step = sizeof(__m256i) / sizeof(float);
259 constexpr qsizetype HalfStep = sizeof(__m128i) / sizeof(float);
260 qsizetype i = 0;
261
262 if (len >= Step) {
263 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
264 __m128i f16 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + offset));
265 __m256 f32 = _mm256_cvtph_ps(f16);
266 _mm256_storeu_ps(out + offset, f32);
267 };
268
269 // main loop: convert Step (8) floats per iteration
270 for ( ; i + Step < len; i += Step)
271 convertOneChunk(i);
272
273 // epilogue: convert the last chunk, possibly overlapping with the last
274 // iteration of the loop
275 return convertOneChunk(len - Step);
276 }
277
278#if QT_COMPILER_SUPPORTS_HERE(AVX512VL) && QT_COMPILER_SUPPORTS_HERE(AVX512BW)
279 if (hasFastF16Avx256())
280 return qFloatFromFloat16_tail_avx256(out, in, len);
281#endif
282
283 if (len >= HalfStep) {
284 auto convertOneChunk = [=](qsizetype offset) QT_FUNCTION_TARGET(F16C) {
285 __m128i f16 = _mm_loadl_epi64(reinterpret_cast<const __m128i *>(in + offset));
286 __m128 f32 = _mm_cvtph_ps(f16);
287 _mm_storeu_ps(out + offset, f32);
288 };
289
290 // two conversions, possibly overlapping
291 convertOneChunk(0);
292 return convertOneChunk(len - HalfStep);
293 }
294
295 // Inlining "qfloat16::operator float()":
296 for ( ; i < len; ++i)
297 out[i] = _mm_cvtss_f32(_mm_cvtph_ps(_mm_cvtsi32_si128(in[i])));
298}
299
300#elif defined(__ARM_FP16_FORMAT_IEEE) && defined(__ARM_NEON__) && (__ARM_FP & 2)
301static inline bool hasFastF16()
302{
303 return true;
304}
305
306static void qFloatToFloat16_fast(quint16 *out, const float *in, qsizetype len) noexcept
307{
308 __fp16 *out_f16 = reinterpret_cast<__fp16 *>(out);
309 qsizetype i = 0;
310 for (; i < len - 3; i += 4)
311 vst1_f16(out_f16 + i, vcvt_f16_f32(vld1q_f32(in + i)));
312 SIMD_EPILOGUE(i, len, 3)
313 out_f16[i] = __fp16(in[i]);
314}
315
316static void qFloatFromFloat16_fast(float *out, const quint16 *in, qsizetype len) noexcept
317{
318 const __fp16 *in_f16 = reinterpret_cast<const __fp16 *>(in);
319 qsizetype i = 0;
320 for (; i < len - 3; i += 4)
321 vst1q_f32(out + i, vcvt_f32_f16(vld1_f16(in_f16 + i)));
322 SIMD_EPILOGUE(i, len, 3)
323 out[i] = float(in_f16[i]);
324}
325#else
326static inline bool hasFastF16()
327{
328 return false;
329}
330
331static void qFloatToFloat16_fast(quint16 *, const float *, qsizetype) noexcept
332{
333 Q_UNREACHABLE();
334}
335
336static void qFloatFromFloat16_fast(float *, const quint16 *, qsizetype) noexcept
337{
338 Q_UNREACHABLE();
339}
340#endif
341/*!
342 \since 5.11
343 \relates qfloat16
344
345 Converts \a len floats from \a in to qfloat16 and stores them in \a out.
346 Both \a in and \a out must have \a len allocated entries.
347
348 This function is faster than converting values one by one, and will do runtime
349 F16C detection on x86 and x86-64 hardware.
350*/
351Q_CORE_EXPORT void qFloatToFloat16(qfloat16 *out, const float *in, qsizetype len) noexcept
352{
353 if (hasFastF16())
354 return qFloatToFloat16_fast(reinterpret_cast<quint16 *>(out), in, len);
355
356 for (qsizetype i = 0; i < len; ++i)
357 out[i] = qfloat16(in[i]);
358}
359
360/*!
361 \since 5.11
362 \relates qfloat16
363
364 Converts \a len qfloat16 from \a in to floats 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 qFloatFromFloat16(float *out, const qfloat16 *in, qsizetype len) noexcept
371{
372 if (hasFastF16())
373 return qFloatFromFloat16_fast(out, reinterpret_cast<const quint16 *>(in), len);
374
375 for (qsizetype i = 0; i < len; ++i)
376 out[i] = float(in[i]);
377}
378
379/*!
380 \fn size_t qfloat16::qHash(qfloat16 key, size_t seed)
381 \since 6.5.3
382 \qhash{qfloat16}
383
384 \note In Qt versions before 6.5, this operation was provided by the
385 qHash(float) overload. In Qt versions 6.5.0 to 6.5.2, this functionality
386 was broken in various ways. In Qt versions 6.5.3 and 6.6 onwards, this
387 overload restores the Qt 6.4 behavior.
388*/
389
390#ifndef QT_NO_DATASTREAM
391/*!
392 \fn qfloat16::operator<<(QDataStream &ds, qfloat16 f)
393 \relates QDataStream
394 \since 5.9
395
396 Writes a floating point number, \a f, to the stream \a ds using
397 the standard IEEE 754 format. Returns a reference to the stream.
398
399 \note In Qt versions prior to 6.3, this was a member function on
400 QDataStream.
401*/
402QDataStream &operator<<(QDataStream &ds, qfloat16 f)
403{
404 return ds << f.b16;
405}
406
407/*!
408 \fn qfloat16::operator>>(QDataStream &ds, qfloat16 &f)
409 \relates QDataStream
410 \since 5.9
411
412 Reads a floating point number from the stream \a ds into \a f,
413 using the standard IEEE 754 format. Returns a reference to the
414 stream.
415
416 \note In Qt versions prior to 6.3, this was a member function on
417 QDataStream.
418*/
419QDataStream &operator>>(QDataStream &ds, qfloat16 &f)
420{
421 return ds >> f.b16;
422}
423#endif
424
425QTextStream &operator>>(QTextStream &ts, qfloat16 &f16)
426{
427 float f;
428 ts >> f;
429 f16 = qfloat16(f);
430 return ts;
431}
432
433QTextStream &operator<<(QTextStream &ts, qfloat16 f)
434{
435 return ts << float(f);
436}
437
438QT_END_NAMESPACE
439
\keyword 16-bit Floating Point Support\inmodule QtCore \inheaderfile QFloat16
Definition qfloat16.h:48
Q_CORE_EXPORT int fpClassify() const noexcept
Definition qfloat16.cpp:145
QTextStream & operator>>(QTextStream &ts, qfloat16 &f16)
Definition qfloat16.cpp:425
static bool hasFastF16()
Definition qfloat16.cpp:326
static void qFloatToFloat16_fast(quint16 *, const float *, qsizetype) noexcept
Definition qfloat16.cpp:331
static void qFloatFromFloat16_fast(float *, const quint16 *, qsizetype) noexcept
Definition qfloat16.cpp:336
QDataStream & operator>>(QDataStream &ds, qfloat16 &f)
Definition qfloat16.cpp:419
Q_CORE_EXPORT void qFloatFromFloat16(float *, const qfloat16 *, qsizetype length) noexcept
Q_CORE_EXPORT void qFloatToFloat16(qfloat16 *, const float *, qsizetype length) noexcept