Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2016 by Southwest Research Institute (R)
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QFloat16>
#include <QMetaType>
#include <QTextStream>
#include <QtCompilerDetection>
#include <private/qcomparisontesthelper_p.h>
#include <math.h>
//#define DO_FULL_TEST
using namespace Qt::StringLiterals;
static_assert(sizeof(float) == sizeof(quint32), "Float not 32-bit");
class tst_qfloat16: public QObject
{
Q_OBJECT
private slots:
void compareCompiles();
void operatorsAreConstexpr();
void ordering_data();
void ordering();
void fuzzyCompare_data();
void fuzzyCompare();
void fuzzyIsNull_data();
void fuzzyIsNull();
void ltgt_data();
void ltgt();
void qNaN();
void infinity();
void float_cast();
void float_cast_data();
void promotionTests();
void arithOps_data();
void arithOps();
#if defined DO_FULL_TEST
void floatToFloat16Full_data();
void floatToFloat16Full();
void floatFromFloat16Full();
#endif
void floatToFloat16();
void floatFromFloat16();
void finite_data();
void finite();
void properties();
void limits();
void mantissaOverflow();
void dataStream();
void textStream();
// std::format support
void formatCompileTime();
void format_data();
void format();
void formatMultiArg();
};
void tst_qfloat16::compareCompiles()
{
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, float>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, double>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, long double>();
#if QFLOAT16_IS_NATIVE
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, qfloat16::NativeType>();
#endif
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, int>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, qint8>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, quint8>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, qint16>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, quint16>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, char16_t>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, long>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, unsigned long>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, wchar_t>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, qint32>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, quint32>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, qint64>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, quint64>();
#ifdef QT_SUPPORTS_INT128
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, qint128>();
QTestPrivate::testAllComparisonOperatorsCompile<qfloat16, quint128>();
#endif
}
template <typename RHS> static constexpr bool checkArithmeticCompiles(qfloat16 lhs, RHS rhs)
{
auto assertIsFloat16Ref = [](auto &v) {
static_assert(std::is_same_v<std::remove_reference_t<decltype(v)>, qfloat16>);
};
auto assertType = [](auto v) {
// C++23 conversion rules (q.v. P1467):
// - FP op integral -> double
// - FP op OtherFP -> the one with higher conversion rank
// all other, supported FP types have a higher rank and are a superset
using Expected = std::conditional_t<std::is_integral_v<RHS>, double, RHS>;
static_assert(std::is_same_v<decltype(v), Expected>);
};
auto ps = +lhs; // unary +
auto ng = -lhs; // unary -
static_assert(std::is_same_v<decltype(ps), qfloat16>);
static_assert(std::is_same_v<decltype(ng), qfloat16>);
assertType(lhs + rhs);
assertType(lhs - rhs);
assertType(lhs * rhs);
assertType(lhs / RHS(1)); // avoid division by zero
assertType(rhs + lhs);
assertType(rhs - lhs);
assertType(rhs * lhs);
assertType(rhs / qfloat16(1.0f)); // avoid division by zero
assertIsFloat16Ref(++lhs);
assertIsFloat16Ref(--lhs);
auto inc = lhs++;
auto dec = lhs--;
static_assert(std::is_same_v<decltype(inc), qfloat16>);
static_assert(std::is_same_v<decltype(dec), qfloat16>);
// operators op=(integral) are missing
if constexpr (!std::is_integral_v<RHS>) {
assertIsFloat16Ref(lhs += rhs);
assertIsFloat16Ref(lhs -= rhs);
assertIsFloat16Ref(lhs *= rhs);
assertIsFloat16Ref(lhs /= RHS(1)); // avoid division by zero
}
return true;
}
void tst_qfloat16::operatorsAreConstexpr()
{
#if QFLOAT16_IS_NATIVE
// relational
#define CHECK_CONSTEXPR(Type) \
do { \
constexpr qfloat16 lhs = qfloat16(0.0f); \
constexpr Type rhs = 1; \
static_assert(lhs < rhs); \
static_assert(rhs >= lhs); \
} while (false)
CHECK_CONSTEXPR(qfloat16);
CHECK_CONSTEXPR(float);
CHECK_CONSTEXPR(double);
CHECK_CONSTEXPR(long double);
CHECK_CONSTEXPR(qfloat16::NativeType);
CHECK_CONSTEXPR(qint8);
CHECK_CONSTEXPR(quint8);
CHECK_CONSTEXPR(qint16);
CHECK_CONSTEXPR(quint16);
CHECK_CONSTEXPR(qint32);
CHECK_CONSTEXPR(quint32);
CHECK_CONSTEXPR(long);
CHECK_CONSTEXPR(unsigned long);
CHECK_CONSTEXPR(qint64);
CHECK_CONSTEXPR(quint64);
#ifdef QT_SUPPORTS_INT128
CHECK_CONSTEXPR(qint128);
CHECK_CONSTEXPR(quint128);
#endif
#undef CHECK_CONSTEXPR
// arithmetic
static_assert(checkArithmeticCompiles({}, qfloat16(1.0f)));
static_assert(checkArithmeticCompiles(qfloat16(0.0f), qfloat16(1.0f)));
#ifdef __STDCPP_FLOAT16_T__
static_assert(checkArithmeticCompiles(qfloat16(0.0f), 1.0f16));
#endif
static_assert(checkArithmeticCompiles(qfloat16(0.0f), 1.0f));
static_assert(checkArithmeticCompiles(qfloat16(0.0f), 1.0));
static_assert(checkArithmeticCompiles(qfloat16(0.0f), 1.0L));
static_assert(checkArithmeticCompiles(qfloat16(0.0f), 1));
// combine
static_assert(+ +qfloat16{} == qfloat16{});
static_assert(+ +qfloat16{} == qfloat16(0.0f));
static_assert(+ +qfloat16(1.0f) == qfloat16(1.0f));
static_assert(-+-qfloat16(1.0f) == qfloat16(1.0f));
static_assert(qfloat16(1.0f) + qfloat16(0.0f) == qfloat16(1.0f));
static_assert(qfloat16(1.0f) - qfloat16(0.0f) == qfloat16(1.0f));
static_assert(qfloat16(1.0f) * qfloat16(0.0f) == qfloat16(0.0f));
static_assert(qfloat16(1.0f) / qfloat16(1.0f) == qfloat16(1.0f));
#else
QSKIP("This check is only relevant for native float16 types");
#endif // QFLOAT16_IS_NATIVE
}
void tst_qfloat16::ordering_data()
{
QTest::addColumn<float>("left");
QTest::addColumn<float>("right");
auto row = [](float left, float right) {
QTest::addRow("%f_vs_%f", left, right) << left << right;
};
row(0.0f, 0.0f);
row(0.000001f, 0.0f);
row(0.0f, 0.000001f);
row(-1.000001f, 1.000001f);
const float nan = std::numeric_limits<float>::quiet_NaN();
const float inf = std::numeric_limits<float>::infinity();
row(nan, nan);
row(nan, inf);
row(inf, nan);
row(-inf, nan);
row(nan, -inf);
row(-inf, inf);
row(inf, -inf);
row(-inf, 0.0f);
row(0.0f, inf);
row(0.0f, nan);
row(nan, 0.0f);
row(2.0f, 314.159f);
row(-314.159f, 2.0f);
row(-2.0f, 314.159f);
row(nan, 314.159f);
row(-314.159f, inf);
row(-inf, 314.159f);
row(2.0f, -inf);
row(-2.0f, nan);
row(-inf, -2.0f);
// testing with values outside qfloat16 range
row(0.0f, 13e5f);
// generateRow(inf, 13e5f); // fails qfloat16 vs qfloat16 and qfloat16 vs int (QTBUG-118193)
row(0.0f, -13e5f);
// generateRow(-inf, -13e5f); // fails qfloat16 vs qfloat16 and qfloat16 vs int (QTBUG-118193)
}
void tst_qfloat16::ordering()
{
QFETCH(float, left);
QFETCH(float, right);
const auto expectedOrder = Qt::compareThreeWay(left, right);
const auto lhs = qfloat16(left);
#define POSTCHECK(msg) \
if (QTest::currentTestFailed()) { qDebug(msg); return; }
#define CHECK_FP(RHS) \
do { \
QTestPrivate::testAllComparisonOperators(lhs, static_cast<RHS>(right), expectedOrder); \
POSTCHECK("qfloat16 vs " #RHS " comparison failed") \
} while (false) \
/* END */
CHECK_FP(qfloat16);
CHECK_FP(float);
CHECK_FP(double);
// Qt built for VxWorks x86_64 and arm64 fails to link due to undefined reference to
// __extendhfxf2 when below check is enabled.
// This has been acknowledged by WindRiver as a bug
// and is being tracked in ticket 00154117
#if !(defined(Q_OS_VXWORKS) && (defined(Q_PROCESSOR_X86_64) || defined(Q_PROCESSOR_ARM_64)))
CHECK_FP(long double);
#endif
#undef CHECK_FP
auto check_int = [=](auto rhs) {
// check that we're in range before converting, otherwise
// [conv.fpint]/1 says it would be UB
using RHS = decltype(rhs);
if (right > double(std::numeric_limits<RHS>::max()))
return;
if (auto min = std::numeric_limits<RHS>::min(); min != 0 && right < double(min))
return;
rhs = RHS(right);
const auto expectedRes = Qt::compareThreeWay(left, rhs);
QTestPrivate::testAllComparisonOperators(lhs, rhs, expectedRes);
};
#define CHECK_INT(RHS) \
do { \
check_int(static_cast<RHS>(0)); \
POSTCHECK("qfloat16 vs " #RHS " comparison failed") \
} while (false) \
/* END */
if (qIsFinite(right)) {
CHECK_INT(int);
CHECK_INT(qint8);
CHECK_INT(signed char);
CHECK_INT(qint16);
CHECK_INT(qint32);
CHECK_INT(qint64);
#ifdef QT_SUPPORTS_INT128
CHECK_INT(qint128);
#endif
if (right >= 0) {
CHECK_INT(unsigned int);
CHECK_INT(quint8);
CHECK_INT(unsigned char);
CHECK_INT(quint16);
CHECK_INT(quint32);
CHECK_INT(quint64);
#ifdef QT_SUPPORTS_INT128
CHECK_INT(quint128);
#endif
}
}
#undef CHECK_INT
#undef POSTCHECK
}
void tst_qfloat16::fuzzyCompare_data()
{
QTest::addColumn<qfloat16>("val1");
QTest::addColumn<qfloat16>("val2");
QTest::addColumn<bool>("fuzEqual");
QTest::addColumn<bool>("isEqual");
QTest::newRow("zero") << qfloat16(0.0f) << qfloat16(0.0f) << true << true;
QTest::newRow("ten") << qfloat16(1e1f) << qfloat16(1e1f) << true << true;
QTest::newRow("large") << qfloat16(1e4f) << qfloat16(1e4f) << true << true;
QTest::newRow("small") << qfloat16(1e-5f) << qfloat16(1e-5f) << true << true;
QTest::newRow("eps") << qfloat16(10.01f) << qfloat16(10.02f) << true << false;
QTest::newRow("eps2") << qfloat16(1024.f) << qfloat16(1033.f) << true << false;
QTest::newRow("mis1") << qfloat16(0.0f) << qfloat16(1.0f) << false << false;
QTest::newRow("mis2") << qfloat16(0.0f) << qfloat16(1e7f) << false << false;
QTest::newRow("mis3") << qfloat16(0.0f) << qfloat16(1e-4f) << false << false;
QTest::newRow("mis4") << qfloat16(1e8f) << qfloat16(1e-8f) << false << false;
QTest::newRow("mis5") << qfloat16(1e-4f) << qfloat16(1e-5) << false << false;
QTest::newRow("mis6") << qfloat16(1024.f) << qfloat16(1034.f) << false << false;
}
void tst_qfloat16::fuzzyCompare()
{
QFETCH(qfloat16, val1);
QFETCH(qfloat16, val2);
QFETCH(bool, fuzEqual);
QFETCH(bool, isEqual);
if (!isEqual && (val1==val2))
qWarning() << "Identical arguments provided unintentionally!";
if (fuzEqual) {
QVERIFY(::qFuzzyCompare(val1, val2));
QVERIFY(::qFuzzyCompare(val2, val1));
QVERIFY(::qFuzzyCompare(-val1, -val2));
QVERIFY(::qFuzzyCompare(-val2, -val1));
} else {
QVERIFY(!::qFuzzyCompare(val1, val2));
QVERIFY(!::qFuzzyCompare(val2, val1));
QVERIFY(!::qFuzzyCompare(-val1, -val2));
QVERIFY(!::qFuzzyCompare(-val2, -val1));
}
}
void tst_qfloat16::fuzzyIsNull_data()
{
QTest::addColumn<qfloat16>("value");
QTest::addColumn<bool>("isNull");
using Bounds = std::numeric_limits<qfloat16>;
const qfloat16 one(1), huge(1000), tiny(0.00976f);
QTest::newRow("zero") << qfloat16(0.0f) << true;
QTest::newRow("min") << Bounds::min() << true;
QTest::newRow("denorm_min") << Bounds::denorm_min() << true;
QTest::newRow("tiny") << tiny << true;
QTest::newRow("deci") << qfloat16(.1) << false;
QTest::newRow("one") << one << false;
QTest::newRow("ten") << qfloat16(10) << false;
QTest::newRow("huge") << huge << false;
}
void tst_qfloat16::fuzzyIsNull()
{
QFETCH(qfloat16, value);
QFETCH(bool, isNull);
QCOMPARE(::qFuzzyIsNull(value), isNull);
QCOMPARE(::qFuzzyIsNull(-value), isNull);
}
void tst_qfloat16::ltgt_data()
{
QTest::addColumn<float>("val1");
QTest::addColumn<float>("val2");
QTest::newRow("zero") << 0.0f << 0.0f;
QTest::newRow("-zero") << -0.0f << 0.0f;
QTest::newRow("ten") << 10.0f << 10.0f;
QTest::newRow("large") << 100000.0f << 100000.0f;
QTest::newRow("small") << 0.0000001f << 0.0000001f;
QTest::newRow("eps") << 10.000000000000001f << 10.00000000000002f;
QTest::newRow("eps2") << 10.000000000000001f << 10.000000000000009f;
QTest::newRow("mis1") << 0.0f << 1.0f;
QTest::newRow("mis2") << 0.0f << 10000000.0f;
QTest::newRow("mis3") << 0.0f << 0.0001f;
QTest::newRow("mis4") << 100000000.0f << 0.000000001f;
QTest::newRow("mis5") << 0.0001f << 0.00001f;
QTest::newRow("45,23") << 45.f << 23.f;
QTest::newRow("1000,76") << 1000.f << 76.f;
}
void tst_qfloat16::ltgt()
{
QFETCH(float, val1);
QFETCH(float, val2);
QCOMPARE(qfloat16(val1) == qfloat16(val2), val1 == val2);
QCOMPARE(qfloat16(val1) < qfloat16(val2), val1 < val2);
QCOMPARE(qfloat16(val1) <= qfloat16(val2), val1 <= val2);
QCOMPARE(qfloat16(val1) > qfloat16(val2), val1 > val2);
QCOMPARE(qfloat16(val1) >= qfloat16(val2), val1 >= val2);
QCOMPARE(qfloat16(val1) == qfloat16(-val2), val1 == -val2);
QCOMPARE(qfloat16(val1) < qfloat16(-val2), val1 < -val2);
QCOMPARE(qfloat16(val1) <= qfloat16(-val2), val1 <= -val2);
QCOMPARE(qfloat16(val1) > qfloat16(-val2), val1 > -val2);
QCOMPARE(qfloat16(val1) >= qfloat16(-val2), val1 >= -val2);
QCOMPARE(qfloat16(-val1) == qfloat16(val2), -val1 == val2);
QCOMPARE(qfloat16(-val1) < qfloat16(val2), -val1 < val2);
QCOMPARE(qfloat16(-val1) <= qfloat16(val2), -val1 <= val2);
QCOMPARE(qfloat16(-val1) > qfloat16(val2), -val1 > val2);
QCOMPARE(qfloat16(-val1) >= qfloat16(val2), -val1 >= val2);
QCOMPARE(qfloat16(-val1) == qfloat16(-val2), -val1 == -val2);
QCOMPARE(qfloat16(-val1) < qfloat16(-val2), -val1 < -val2);
QCOMPARE(qfloat16(-val1) <= qfloat16(-val2), -val1 <= -val2);
QCOMPARE(qfloat16(-val1) > qfloat16(-val2), -val1 > -val2);
QCOMPARE(qfloat16(-val1) >= qfloat16(-val2), -val1 >= -val2);
}
#if defined __FAST_MATH__ && (__GNUC__ * 100 + __GNUC_MINOR__ >= 404)
// turn -ffast-math off
# pragma GCC optimize "no-fast-math"
#endif
void tst_qfloat16::qNaN()
{
#if defined __FAST_MATH__ && (__GNUC__ * 100 + __GNUC_MINOR__ < 404)
QSKIP("Non-conformant fast math mode is enabled, cannot run test");
#endif
using Bounds = std::numeric_limits<qfloat16>;
const qfloat16 nan = Bounds::quiet_NaN();
const qfloat16 zero(0), one(1);
QVERIFY(!(zero > nan));
QVERIFY(!(zero < nan));
QVERIFY(!(zero == nan));
QVERIFY(!qIsInf(nan));
QVERIFY(qIsNaN(nan));
QVERIFY(qIsNaN(nan + one));
QVERIFY(qIsNaN(-nan));
QVERIFY(qIsNaN(nan * zero));
QVERIFY(qIsNaN(Bounds::infinity() * zero));
QVERIFY(!nan.isNormal());
QVERIFY(!qIsFinite(nan));
QVERIFY(!(nan == nan));
QCOMPARE(nan, nan); // Despite the preceding
QCOMPARE(qFpClassify(nan), FP_NAN);
}
void tst_qfloat16::infinity()
{
const qfloat16 huge = std::numeric_limits<qfloat16>::infinity();
const qfloat16 zero(0), one(1), two(2);
QVERIFY(huge > -huge);
QVERIFY(huge > zero);
QVERIFY(-huge < zero);
QCOMPARE(huge, huge);
QCOMPARE(-huge, -huge);
QCOMPARE(one / huge, zero);
QVERIFY(qFuzzyCompare(one / huge, zero)); // (same thing)
QVERIFY(qIsInf(huge));
QVERIFY(qIsInf(-huge));
QVERIFY(qIsInf(two * huge));
QVERIFY(qIsInf(huge * two));
QVERIFY(!huge.isNormal());
QVERIFY(!(-huge).isNormal());
QVERIFY(!qIsNaN(huge));
QVERIFY(!qIsNaN(-huge));
QVERIFY(!qIsFinite(huge));
QVERIFY(!qIsFinite(-huge));
QCOMPARE(qFpClassify(huge), FP_INFINITE);
QCOMPARE(qFpClassify(-huge), FP_INFINITE);
}
void tst_qfloat16::float_cast_data()
{
QTest::addColumn<float>("val");
QTest::newRow("zero") << 0.f;
QTest::newRow("one") << 1e0f;
QTest::newRow("ten") << 1e1f;
QTest::newRow("hund") << 1e2f;
QTest::newRow("thou") << 1e3f;
QTest::newRow("tthou") << 1e4f;
//QTest::newRow("hthou") << 1e5f;
//QTest::newRow("mil") << 1e6f;
//QTest::newRow("tmil") << 1e7f;
//QTest::newRow("hmil") << 1e8f;
}
void tst_qfloat16::float_cast()
{
QFETCH(float, val);
QVERIFY(qFuzzyCompare((float)(qfloat16(val)),val));
QVERIFY(qFuzzyCompare((float)(qfloat16(-val)),-val));
QVERIFY(qFuzzyCompare((double)(qfloat16(val)),(double)(val)));
QVERIFY(qFuzzyCompare((double)(qfloat16(-val)),(double)(-val)));
//QVERIFY(qFuzzyCompare((long double)(qfloat16(val)),(long double)(val)));
//QVERIFY(qFuzzyCompare((long double)(qfloat16(-val)),(long double)(-val)));
}
void tst_qfloat16::promotionTests()
{
QCOMPARE(sizeof(qfloat16),sizeof(qfloat16(1.f)+qfloat16(1.f)));
QCOMPARE(sizeof(qfloat16),sizeof(qfloat16(1.f)-qfloat16(1.f)));
QCOMPARE(sizeof(qfloat16),sizeof(qfloat16(1.f)*qfloat16(1.f)));
QCOMPARE(sizeof(qfloat16),sizeof(qfloat16(1.f)/qfloat16(1.f)));
QCOMPARE(sizeof(float),sizeof(1.f+qfloat16(1.f)));
QCOMPARE(sizeof(float),sizeof(1.f-qfloat16(1.f)));
QCOMPARE(sizeof(float),sizeof(1.f*qfloat16(1.f)));
QCOMPARE(sizeof(float),sizeof(1.f/qfloat16(1.f)));
QCOMPARE(sizeof(float),sizeof(qfloat16(1.f)+1.f));
QCOMPARE(sizeof(float),sizeof(qfloat16(1.f)-1.f));
QCOMPARE(sizeof(float),sizeof(qfloat16(1.f)*1.f));
QCOMPARE(sizeof(float),sizeof(qfloat16(1.f)/1.f));
QCOMPARE(sizeof(double),sizeof(1.+qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(1.-qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(1.*qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(1./qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)+1.));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)-1.));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)*1.));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)/1.));
QCOMPARE(sizeof(long double),sizeof((long double)(1.)+qfloat16(1.f)));
QCOMPARE(sizeof(long double),sizeof((long double)(1.)-qfloat16(1.f)));
QCOMPARE(sizeof(long double),sizeof((long double)(1.)*qfloat16(1.f)));
QCOMPARE(sizeof(long double),sizeof((long double)(1.)/qfloat16(1.f)));
QCOMPARE(sizeof(long double),sizeof(qfloat16(1.f)+(long double)(1.)));
QCOMPARE(sizeof(long double),sizeof(qfloat16(1.f)-(long double)(1.)));
QCOMPARE(sizeof(long double),sizeof(qfloat16(1.f)*(long double)(1.)));
QCOMPARE(sizeof(long double),sizeof(qfloat16(1.f)/(long double)(1.)));
QCOMPARE(sizeof(double),sizeof(1+qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(1-qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(1*qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(1/qfloat16(1.f)));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)+1));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)-1));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)*1));
QCOMPARE(sizeof(double),sizeof(qfloat16(1.f)/1));
QCOMPARE(QString::number(1.f),QString::number(double(qfloat16(1.f))));
}
void tst_qfloat16::arithOps_data()
{
QTest::addColumn<float>("val1");
QTest::addColumn<float>("val2");
QTest::newRow("zero") << 0.0f << 2.0f;
QTest::newRow("one") << 1.0f << 4.0f;
QTest::newRow("ten") << 10.0f << 20.0f;
}
void tst_qfloat16::arithOps()
{
QFETCH(float, val1);
QFETCH(float, val2);
checkArithmeticCompiles(qfloat16(val1), qfloat16(val2));
checkArithmeticCompiles(qfloat16(val1), val2);
checkArithmeticCompiles(qfloat16(val1), val2 + 0.0);
checkArithmeticCompiles(qfloat16(val1), val2 + 0.0L);
checkArithmeticCompiles(qfloat16(val1), int(val2));
QVERIFY(qFuzzyCompare(float(qfloat16(val1) + qfloat16(val2)), val1 + val2));
QVERIFY(qFuzzyCompare(float(qfloat16(val1) - qfloat16(val2)), val1 - val2));
QVERIFY(qFuzzyCompare(float(qfloat16(val1) * qfloat16(val2)), val1 * val2));
QVERIFY(qFuzzyCompare(float(qfloat16(val1) / qfloat16(val2)), val1 / val2));
QVERIFY(qFuzzyCompare(qfloat16(val1) + val2, val1 + val2));
QVERIFY(qFuzzyCompare(qfloat16(val1) - val2, val1 - val2));
QVERIFY(qFuzzyCompare(qfloat16(val1) * val2, val1 * val2));
QVERIFY(qFuzzyCompare(qfloat16(val1) / val2, val1 / val2));
QVERIFY(qFuzzyCompare(val1 + qfloat16(val2), val1 + val2));
QVERIFY(qFuzzyCompare(val1 - qfloat16(val2), val1 - val2));
QVERIFY(qFuzzyCompare(val1 * qfloat16(val2), val1 * val2));
QVERIFY(qFuzzyCompare(val1 / qfloat16(val2), val1 / val2));
qfloat16 v{val1};
QCOMPARE(float(++v), val1 + 1);
QCOMPARE(float(v), val1 + 1);
QCOMPARE(float(--v), val1);
QCOMPARE(float(v), val1);
QCOMPARE(float(v--), val1);
QCOMPARE(float(v), val1 - 1);
QCOMPARE(float(v++), val1 - 1);
QCOMPARE(float(v), val1);
float r1 = 0.f;
r1 += qfloat16(val2);
QVERIFY(qFuzzyCompare(r1,val2));
float r2 = 0.f;
r2 -= qfloat16(val2);
QVERIFY(qFuzzyCompare(r2,-val2));
float r3 = 1.f;
r3 *= qfloat16(val2);
QVERIFY(qFuzzyCompare(r3,val2));
float r4 = 1.f;
r4 /= qfloat16(val2);
QVERIFY(qFuzzyCompare(r4,1.f/val2));
}
#if defined DO_FULL_TEST
void tst_qfloat16::floatToFloat16Full_data()
{
QTest::addColumn<quint32>("group");
for (quint32 j = 0x00; j < 0x100; ++j)
QTest::addRow("%02x", j) << j;
}
void tst_qfloat16::floatToFloat16Full()
{
QFETCH(quint32, group);
for (quint32 j = 0x00; j < 0x100; ++j) {
quint32 data[1<<16];
qfloat16 out[1<<16];
qfloat16 expected[1<<16];
float in[1<<16];
for (int i = 0; i < (1<<16); ++i)
data[i] = (group << 24) | (j << 16) | i;
memcpy(in, data, (1<<16)*sizeof(float));
for (int i = 0; i < (1<<16); ++i)
expected[i] = qfloat16(in[i]);
qFloatToFloat16(out, in, 1<<16);
for (int i = 0; i < (1<<16); ++i) {
if (out[i] != expected[i])
QVERIFY(qIsNaN(out[i]) && qIsNaN(expected[i]));
}
}
}
void tst_qfloat16::floatFromFloat16Full()
{
quint16 data[1<<16];
float out[1<<16];
float expected[1<<16];
for (int i = 0; i < (1<<16); ++i)
data[i] = i;
const qfloat16 *in = reinterpret_cast<const qfloat16 *>(data);
for (int i = 0; i < (1<<16); ++i)
expected[i] = float(in[i]);
qFloatFromFloat16(out, in, 1<<16);
for (int i = 0; i < (1<<16); ++i)
if (out[i] != expected[i])
QVERIFY(qIsNaN(out[i]) && qIsNaN(expected[i]));
}
#endif
QT_WARNING_PUSH
// checked stack use and deemed it acceptable
QT_WARNING_DISABLE_LARGE_STACK_FRAME
void tst_qfloat16::floatToFloat16()
{
constexpr int count = 10000;
float in[count];
qfloat16 out[count];
qfloat16 expected[count];
for (int i = 0; i < count; ++i)
in[i] = (i - count/2) * (1/13.f);
for (int i = 0; i < count; ++i)
expected[i] = qfloat16(in[i]);
qFloatToFloat16(out, in, count);
for (int i = 0; i < count; ++i)
QVERIFY(out[i] == expected[i]);
}
QT_WARNING_POP
void tst_qfloat16::floatFromFloat16()
{
qfloat16 in[35];
float out[35];
float expected[35];
for (int i = 0; i < 35; ++i)
in[i] = qfloat16(i * (17.f / 3));
for (int i = 0; i < 35; ++i)
expected[i] = float(in[i]);
qFloatFromFloat16(out, in, 35);
for (int i = 0; i < 35; ++i)
QCOMPARE(out[i], expected[i]);
}
static qfloat16 powf16(qfloat16 base, int raise)
{
const qfloat16 one(1.f);
if (raise < 0) {
raise = -raise;
base = one / base;
}
qfloat16 answer = (raise & 1) ? base : one;
while (raise > 0) {
raise >>= 1;
base *= base;
if (raise & 1)
answer *= base;
}
return answer;
}
void tst_qfloat16::finite_data()
{
using Bounds = std::numeric_limits<qfloat16>;
QTest::addColumn<qfloat16>("value");
QTest::addColumn<int>("mode");
QTest::newRow("zero") << qfloat16(0) << FP_ZERO;
QTest::newRow("-zero") << -qfloat16(0) << FP_ZERO;
QTest::newRow("one") << qfloat16(1) << FP_NORMAL;
QTest::newRow("-one") << qfloat16(-1) << FP_NORMAL;
QTest::newRow("ten") << qfloat16(10) << FP_NORMAL;
QTest::newRow("-ten") << qfloat16(-10) << FP_NORMAL;
QTest::newRow("max") << Bounds::max() << FP_NORMAL;
QTest::newRow("lowest") << Bounds::lowest() << FP_NORMAL;
QTest::newRow("min") << Bounds::min() << FP_NORMAL;
QTest::newRow("-min") << -Bounds::min() << FP_NORMAL;
QTest::newRow("denorm_min") << Bounds::denorm_min() << FP_SUBNORMAL;
QTest::newRow("-denorm_min") << -Bounds::denorm_min() << FP_SUBNORMAL;
}
void tst_qfloat16::finite()
{
QFETCH(qfloat16, value);
QFETCH(int, mode);
QCOMPARE(value.isNormal(), mode == FP_NORMAL);
QCOMPARE(value, value); // Fuzzy
QVERIFY(value == value); // Exact
QVERIFY(qIsFinite(value));
QVERIFY(!qIsInf(value));
QVERIFY(!qIsNaN(value));
QCOMPARE(qFpClassify(value), mode);
// *NOT* using QCOMPARE() on finite qfloat16 values, since that uses fuzzy
// comparison, and we need exact here.
const qfloat16 zero(0), plus(+1), minus(-1);
const qfloat16 magnitude = (value < zero) ? -value : value;
using std::copysign;
QCOMPARE_EQ(copysign(value, plus), magnitude);
QCOMPARE_EQ(copysign(value, minus), -magnitude);
}
void tst_qfloat16::properties()
{
using Bounds = std::numeric_limits<qfloat16>;
QVERIFY(Bounds::is_specialized);
QVERIFY(Bounds::is_signed);
QVERIFY(!Bounds::is_integer);
QVERIFY(!Bounds::is_exact);
// While we'd like to check for __STDC_IEC_559__, as per ISO/IEC 9899:2011
// Annex F (C11, normative for C++11), there are a few corner cases regarding
// denormals where GHS compiler is relying hardware behavior that is not IEC
// 559 compliant.
// On GHS the compiler reports std::numeric_limits<float>::is_iec559 as false.
// and the same supposed to be for qfloat16.
#if !defined(Q_CC_GHS)
QVERIFY(Bounds::is_iec559);
#endif //Q_CC_GHS
#if QT_CONFIG(signaling_nan)
// Technically, presence of NaN and infinities are implied from the above check, but that checkings GHS compiler complies.
QVERIFY(Bounds::has_infinity && Bounds::has_quiet_NaN && Bounds::has_signaling_NaN);
#endif
QVERIFY(Bounds::is_bounded);
QVERIFY(!Bounds::is_modulo);
QVERIFY(!Bounds::traps);
QVERIFY(Bounds::has_infinity);
QVERIFY(Bounds::has_quiet_NaN);
#if QT_CONFIG(signaling_nan)
QVERIFY(Bounds::has_signaling_NaN);
#endif
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED // has_denorm is deprecated in C++23
#if !defined(Q_CC_GHS)
QCOMPARE(Bounds::has_denorm, std::denorm_present);
#else
// For GHS compiler the "denorm_indeterminite" is the expected return value.
QCOMPARE(Bounds::has_denorm, std::denorm_indeterminate);
#endif // Q_CC_GHS
QT_WARNING_POP
QCOMPARE(Bounds::round_style, std::round_to_nearest);
QCOMPARE(Bounds::radix, 2);
// Untested: has_denorm_loss
}
void tst_qfloat16::limits() // See also: qNaN() and infinity()
{
// *NOT* using QCOMPARE() on finite qfloat16 values, since that uses fuzzy
// comparison, and we need exact here.
using Bounds = std::numeric_limits<qfloat16>;
// A few useful values:
const qfloat16 zero(0), one(1), ten(10);
// The specifics of minus zero:
// (IEEE 754 seems to want -zero < zero, but -0. == 0. and -0.f == 0.f in C++.)
QVERIFY(-zero <= zero);
QVERIFY(-zero == zero);
QVERIFY(!(-zero > zero));
// digits in the mantissa, including the implicit 1 before the binary dot at its left:
QVERIFY(qfloat16(1 << (Bounds::digits - 1)) + one > qfloat16(1 << (Bounds::digits - 1)));
QVERIFY(qfloat16(1 << Bounds::digits) + one == qfloat16(1 << Bounds::digits));
// There is a wilful of-by-one in how m(ax|in)_exponent are defined; they're
// the lowest and highest n for which radix^{n-1} are normal and finite.
const qfloat16 two(Bounds::radix);
qfloat16 bit = powf16(two, Bounds::max_exponent - 1);
QVERIFY(qIsFinite(bit));
QVERIFY(qIsInf(bit * two));
bit = powf16(two, Bounds::min_exponent - 1);
QVERIFY(bit.isNormal());
QCOMPARE(qFpClassify(bit), FP_NORMAL);
QVERIFY(!(bit / two).isNormal());
QCOMPARE(qFpClassify(bit / two), FP_SUBNORMAL);
QVERIFY(bit / two > zero);
// Base ten (with no matching off-by-one idiocy):
// the lowest negative number n such that 10^n is a valid normalized value
qfloat16 low10(powf16(ten, Bounds::min_exponent10));
QVERIFY(low10 > zero);
QVERIFY(low10.isNormal());
low10 /= ten;
QVERIFY(low10 == zero || !low10.isNormal());
// the largest positive number n such that 10^n is a representable finite value
qfloat16 high10(powf16(ten, Bounds::max_exponent10));
QVERIFY(high10 > zero);
QVERIFY(qIsFinite(high10));
QVERIFY(!qIsFinite(high10 * ten));
QCOMPARE(qFpClassify(high10), FP_NORMAL);
// How many digits are significant ? (Casts avoid linker errors ...)
QCOMPARE(int(Bounds::digits10), 3); // ~9.88e-4 has enough sigificant digits:
qfloat16 below(9.876e-4f), above(9.884e-4f); // both round to ~9.88e-4
QVERIFY(below == above);
QCOMPARE(int(Bounds::max_digits10), 5); // we need 5 to distinguish these two:
QVERIFY(qfloat16(1000.5f) != qfloat16(1001.4f));
// Actual limiting values of the type:
const qfloat16 rose(one + Bounds::epsilon());
QVERIFY(rose > one);
QVERIFY(one + Bounds::epsilon() / two == one);
QVERIFY(Bounds::max() > zero);
QVERIFY(qIsInf(Bounds::max() * rose));
QVERIFY(Bounds::lowest() < zero);
QVERIFY(qIsInf(Bounds::lowest() * rose));
QVERIFY(Bounds::min() > zero);
QVERIFY(!(Bounds::min() / rose).isNormal());
QVERIFY(Bounds::denorm_min() > zero);
QVERIFY(Bounds::denorm_min() / two == zero);
const qfloat16 under = (-Bounds::denorm_min()) / two;
QVERIFY(under == -zero);
using std::copysign;
QCOMPARE_EQ(copysign(qfloat16(1), under), qfloat16(-1));
}
void tst_qfloat16::mantissaOverflow()
{
// Test we don't change category due to mantissa overflow when rounding.
quint32 in = 0x7fffffff;
float f;
memcpy(&f, &in, 4);
qfloat16 f16 = qfloat16(f);
qfloat16 f16s[1];
qFloatToFloat16(f16s, &f, 1);
QCOMPARE(f16, f16s[0]);
QVERIFY(qIsNaN(f16));
}
void tst_qfloat16::dataStream()
{
QByteArray ba;
QDataStream ds(&ba, QIODevice::ReadWrite);
ds << qfloat16(1.5) << qfloat16(-1);
QCOMPARE(ba.size(), 4);
QCOMPARE(ds.status(), QDataStream::Ok);
QCOMPARE(ba, QByteArray("\x3e\0\xbc\0", 4));
ds.device()->seek(0);
ds.resetStatus();
ds.setByteOrder(QDataStream::LittleEndian);
ds << qfloat16(0) << qfloat16(-1);
QCOMPARE(ds.status(), QDataStream::Ok);
QCOMPARE(ba, QByteArray("\0\0\0\xbc", 4));
ds.device()->seek(0);
ds.resetStatus();
qfloat16 zero = 1;
ds >> zero;
QCOMPARE(ds.status(), QDataStream::Ok);
QCOMPARE(zero, qfloat16(0));
ds.device()->seek(0);
ds.resetStatus();
QMetaType mt = QMetaType(QMetaType::Float16);
QVERIFY(mt.save(ds, &zero));
ds.device()->seek(0);
ds.resetStatus();
zero = -1;
QVERIFY(mt.load(ds, &zero));
QCOMPARE(zero, qfloat16(0));
}
void tst_qfloat16::textStream()
{
QString buffer;
{
QTextStream ts(&buffer);
ts << qfloat16(0) << Qt::endl << qfloat16(1.5);
QCOMPARE(ts.status(), QTextStream::Ok);
}
QCOMPARE(buffer, "0\n1.5");
{
QTextStream ts(&buffer);
qfloat16 zero = qfloat16(-2.5), threehalves = 1234;
ts >> zero >> threehalves;
QCOMPARE(ts.status(), QTextStream::Ok);
QCOMPARE(zero, qfloat16(0));
QCOMPARE(threehalves, 1.5);
}
}
void tst_qfloat16::formatCompileTime()
{
#ifdef QT_SUPPORTS_STD_FORMAT
// Starting from __cpp_lib_format == 202106L,
// std::format requires the format string to be evaluated at compile-time,
// so check it here.
const qfloat16 val{1.234f};
std::locale loc{"C"};
// char
std::string buffer;
std::format_to(std::back_inserter(buffer), "{}", val);
std::format_to(std::back_inserter(buffer), "{:*>15.7f}", val);
std::format_to(std::back_inserter(buffer), "{:*^+#15.7g}", val);
std::format_to(std::back_inserter(buffer), "{:*<-#15.7A}", val);
std::format_to(std::back_inserter(buffer), "{:*^ 15.7e}", val);
std::format_to(std::back_inserter(buffer), loc, "{:*^10.3Lf}", val);
std::format_to(std::back_inserter(buffer), loc, "{:*< 10.7LE}", val);
// wchar_t
std::wstring wbuffer;
std::format_to(std::back_inserter(wbuffer), L"{}", val);
std::format_to(std::back_inserter(wbuffer), L"{:*>15.7f}", val);
std::format_to(std::back_inserter(wbuffer), L"{:*^+#15.7g}", val);
std::format_to(std::back_inserter(wbuffer), L"{:*<-#15.7A}", val);
std::format_to(std::back_inserter(wbuffer), L"{:*^ 15.7e}", val);
std::format_to(std::back_inserter(wbuffer), loc, L"{:*^10.3Lf}", val);
std::format_to(std::back_inserter(wbuffer), loc, L"{:*< 10.7LE}", val);
#else
QSKIP("This test requires std::format support!");
#endif // QT_SUPPORTS_STD_FORMAT
}
void tst_qfloat16::format_data()
{
#ifdef QT_SUPPORTS_STD_FORMAT
QTest::addColumn<QString>("format");
QTest::addColumn<qfloat16>("value");
QTest::addColumn<std::locale>("locale");
QTest::addColumn<QString>("expectedString");
auto row = [](const QString &format, qfloat16 val, const QString &expected,
const std::locale &loc = std::locale::classic())
{
QTest::addRow("%s:%s", loc.name().c_str(), qPrintable(format))
<< format << val << loc << expected;
};
row(u"{}"_s, qfloat16(1.f), u"1"_s);
row(u"{:#}"_s, qfloat16(1.f), u"1."_s);
row(u"{:f}"_s, qfloat16(1.f), u"1.000000"_s);
row(u"{:*>10.2a}"_s, qfloat16(-1.23f), u"**-1.3bp+0"_s);
try {
// check if this locale is a) valid and b) works as expected
#if defined(Q_CC_MSVC)
std::locale loc("de-DE");
#else
std::locale loc("de_DE");
#endif
if (std::format(loc, "{:L}", 1.25) == "1,25") {
row(u"{:+Lf}"_s, qfloat16(1.f), u"+1,000000"_s, loc);
row(u"{:*^10.3LF}"_s, qfloat16(-0.1234f), u"**-0,123**"_s, loc);
row(u"{:*^#10.4Lg}"_s, qfloat16(-1.f), u"**-1,000**"_s, loc);
row(u"{:*<14.3LE}"_s, qfloat16(-0.1234f), u"-1,234E-01****"_s, loc);
}
} catch (const std::runtime_error &) {
// locale doesn't exist (std::locale constructor threw)
}
#else
QSKIP("This test requires std::format support!");
#endif // QT_SUPPORTS_STD_FORMAT
}
void tst_qfloat16::format()
{
#ifdef QT_SUPPORTS_STD_FORMAT
QFETCH(const QString, format);
QFETCH(const qfloat16, value);
QFETCH(const std::locale, locale);
QFETCH(const QString, expectedString);
// char
{
std::string buffer;
const auto formatStr = format.toStdString();
std::vformat_to(std::back_inserter(buffer), locale, formatStr,
std::make_format_args(value));
const QString actualString = QString::fromStdString(buffer);
QCOMPARE_EQ(actualString, expectedString);
}
// wchar_t
{
std::wstring buffer;
const auto formatStr = format.toStdWString();
std::vformat_to(std::back_inserter(buffer), locale, formatStr,
std::make_wformat_args(value));
const QString actualString = QString::fromStdWString(buffer);
QCOMPARE_EQ(actualString, expectedString);
}
#else
QSKIP("This test requires std::format support!");
#endif // QT_SUPPORTS_STD_FORMAT
}
void tst_qfloat16::formatMultiArg()
{
#ifdef QT_SUPPORTS_STD_FORMAT
const qfloat16 v1{-0.1234f};
const qfloat16 v2{5.67f};
const QString expectedString = u"**+5.67**_*****-1.234E-01"_s;
// char
{
std::string buffer;
std::format_to(std::back_inserter(buffer), "{1:*^+9.2f}_{0:*>15.3E}", v1, v2);
QCOMPARE_EQ(QString::fromStdString(buffer), expectedString);
}
// wchar_t
{
std::wstring buffer;
std::format_to(std::back_inserter(buffer), L"{1:*^+9.2f}_{0:*>15.3E}", v1, v2);
QCOMPARE_EQ(QString::fromStdWString(buffer), expectedString);
}
#else
QSKIP("This test requires std::format support!");
#endif // QT_SUPPORTS_STD_FORMAT
}
QTEST_APPLESS_MAIN(tst_qfloat16)
#include "tst_qfloat16.moc"