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