Reorganise the qfloat16 auto-test

Its limits() test was rather large and had some overlap with an older
qNan() test, that needed some clean-up (it combined qfloat16 values
with double and float values in ways that caused qfloat16 to be
promoted to another type, so we weren't testing qfloat16).

Renamed the qNan() test to qNaN(), separated out the parts of it that
actually tested infinity. Moved various parts of limits() to these and
rationalised the result. Split out a properties() test from limits()
for the properties of the qfloat16 type that are supplied by its
numeric_limits. Split out a data-driven finite() test to cover some
repeated code that was in limits() and extended it to test more
values. Added more tests of isNormal().

Fixed my earlier UK-ish spelling of "optimise", in the process, and
identify the processor rather than the virtualization as the context
where the compiler errs.

Change-Id: I8133da6fb7995ee20e5802c6357d611c8c0cba73
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2019-09-17 18:19:59 +02:00
parent 2e068c7f2a
commit b4efdd3769

View File

@ -41,7 +41,8 @@ private slots:
void fuzzyCompare();
void ltgt_data();
void ltgt();
void qNan();
void qNaN();
void infinity();
void float_cast();
void float_cast_data();
void promotionTests();
@ -49,6 +50,9 @@ private slots:
void arithOps();
void floatToFloat16();
void floatFromFloat16();
void finite_data();
void finite();
void properties();
void limits();
};
@ -154,38 +158,66 @@ void tst_qfloat16::ltgt()
# pragma GCC optimize "no-fast-math"
#endif
void tst_qfloat16::qNan()
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
qfloat16 nan = qQNaN();
QVERIFY(!(0. > nan));
QVERIFY(!(0. < nan));
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 + 1.f));
QVERIFY(qIsNaN(nan + one));
QVERIFY(qIsNaN(-nan));
qfloat16 inf = qInf();
QVERIFY(inf > qfloat16(0));
QVERIFY(-inf < qfloat16(0));
QVERIFY(!qIsNaN(inf));
QVERIFY(qIsInf(inf));
QVERIFY(qIsInf(-inf));
QVERIFY(qIsInf(2.f*inf));
QVERIFY(qIsInf(inf*2.f));
// QTBUG-75812: QEMU/arm64 compiler over-optimizes, so flakily fails 1/inf == 0 :-(
if (qfloat16(9.785e-4f) == qfloat16(9.794e-4f))
QCOMPARE(qfloat16(1.f) / inf, qfloat16(0.f));
#ifdef Q_CC_INTEL
QEXPECT_FAIL("", "ICC optimizes zero * anything to zero", Continue);
#endif
QVERIFY(qIsNaN(nan*0.f));
QVERIFY(qIsNaN(nan * zero));
#ifdef Q_CC_INTEL
QEXPECT_FAIL("", "ICC optimizes zero * anything to zero", Continue);
#endif
QVERIFY(qIsNaN(inf*0.f));
QVERIFY(qFuzzyCompare(qfloat16(1.f/inf), qfloat16(0.0)));
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);
// QTBUG-75812 - see overOptimized in the limits() test.
if (qfloat16(9.785e-4f) == qfloat16(9.794e-4f)) {
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()
@ -366,10 +398,40 @@ static qfloat16 powf16(qfloat16 base, int raise)
return answer;
}
void tst_qfloat16::limits()
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("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_SUBNORMAL);
QCOMPARE(value, value); // Fuzzy
QVERIFY(value == value); // Exact
QVERIFY(qIsFinite(value));
QVERIFY(!qIsInf(value));
QVERIFY(!qIsNaN(value));
QCOMPARE(qFpClassify(value), mode);
}
void tst_qfloat16::properties()
{
// *NOT* using QCOMPARE() on finite qfloat16 values, since that uses fuzzy
// comparison, and we need exact here.
using Bounds = std::numeric_limits<qfloat16>;
QVERIFY(Bounds::is_specialized);
QVERIFY(Bounds::is_signed);
@ -386,21 +448,16 @@ void tst_qfloat16::limits()
QCOMPARE(Bounds::round_style, std::round_to_nearest);
QCOMPARE(Bounds::radix, 2);
// Untested: has_denorm_loss
}
// A few common values:
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);
QVERIFY(qIsFinite(zero));
QVERIFY(!qIsInf(zero));
QVERIFY(!qIsNaN(zero));
QCOMPARE(qFpClassify(zero), FP_ZERO);
QVERIFY(qIsFinite(one));
QVERIFY(!qIsInf(one));
QCOMPARE(qFpClassify(one), FP_NORMAL);
QVERIFY(!qIsNaN(one));
QVERIFY(qIsFinite(ten));
QVERIFY(!qIsInf(ten));
QVERIFY(!qIsNaN(ten));
QCOMPARE(qFpClassify(ten), FP_NORMAL);
// 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)));
@ -436,12 +493,12 @@ void tst_qfloat16::limits()
// How many digits are significant ? (Casts avoid linker errors ...)
QCOMPARE(int(Bounds::digits10), 3); // 9.79e-4 has enough sigificant digits:
qfloat16 below(9.785e-4f), above(9.794e-4f);
#if 0 // Sadly, the QEMU x-compile for arm64 "optimises" comparisons:
const bool overOptimised = false;
#if 0 // Sadly, the QEMU x-compile for arm64 "optimizes" comparisons:
const bool overOptimized = false;
#else
const bool overOptimised = (below != above);
if (overOptimised)
QEXPECT_FAIL("", "Over-optimised on QEMU", Continue);
const bool overOptimized = (below != above);
if (overOptimized)
QEXPECT_FAIL("", "Over-optimized on ARM", Continue);
#endif // (but it did, so should, pass everywhere else, confirming digits10 is indeed 3).
QVERIFY(below == above);
QCOMPARE(int(Bounds::max_digits10), 5); // we need 5 to distinguish these two:
@ -450,62 +507,23 @@ void tst_qfloat16::limits()
// Actual limiting values of the type:
const qfloat16 rose(one + Bounds::epsilon());
QVERIFY(rose > one);
if (overOptimised)
QEXPECT_FAIL("", "Over-optimised on QEMU", Continue);
if (overOptimized)
QEXPECT_FAIL("", "Over-optimized on ARM", Continue);
QVERIFY(one + Bounds::epsilon() / rose == one);
QVERIFY(qIsInf(Bounds::infinity()));
QVERIFY(!qIsNaN(Bounds::infinity()));
QVERIFY(!qIsFinite(Bounds::infinity()));
QCOMPARE(Bounds::infinity(), Bounds::infinity());
QCOMPARE(qFpClassify(Bounds::infinity()), FP_INFINITE);
QVERIFY(Bounds::infinity() > -Bounds::infinity());
QVERIFY(Bounds::infinity() > zero);
QVERIFY(qIsInf(-Bounds::infinity()));
QVERIFY(!qIsNaN(-Bounds::infinity()));
QVERIFY(!qIsFinite(-Bounds::infinity()));
QCOMPARE(-Bounds::infinity(), -Bounds::infinity());
QCOMPARE(qFpClassify(-Bounds::infinity()), FP_INFINITE);
QVERIFY(-Bounds::infinity() < zero);
QVERIFY(qIsNaN(Bounds::quiet_NaN()));
QVERIFY(!qIsInf(Bounds::quiet_NaN()));
QVERIFY(!qIsFinite(Bounds::quiet_NaN()));
QVERIFY(!(Bounds::quiet_NaN() == Bounds::quiet_NaN()));
QCOMPARE(Bounds::quiet_NaN(), Bounds::quiet_NaN());
QCOMPARE(qFpClassify(Bounds::quiet_NaN()), FP_NAN);
QVERIFY(Bounds::max() > zero);
QVERIFY(qIsFinite(Bounds::max()));
QVERIFY(!qIsInf(Bounds::max()));
QVERIFY(!qIsNaN(Bounds::max()));
QVERIFY(qIsInf(Bounds::max() * rose));
QCOMPARE(qFpClassify(Bounds::max()), FP_NORMAL);
QVERIFY(Bounds::lowest() < zero);
QVERIFY(qIsFinite(Bounds::lowest()));
QVERIFY(!qIsInf(Bounds::lowest()));
QVERIFY(!qIsNaN(Bounds::lowest()));
QVERIFY(qIsInf(Bounds::lowest() * rose));
QCOMPARE(qFpClassify(Bounds::lowest()), FP_NORMAL);
QVERIFY(Bounds::min() > zero);
QVERIFY(Bounds::min().isNormal());
QVERIFY(!(Bounds::min() / rose).isNormal());
QVERIFY(qIsFinite(Bounds::min()));
QVERIFY(!qIsInf(Bounds::min()));
QVERIFY(!qIsNaN(Bounds::min()));
QCOMPARE(qFpClassify(Bounds::min()), FP_NORMAL);
QVERIFY(Bounds::denorm_min() > zero);
QVERIFY(!Bounds::denorm_min().isNormal());
QVERIFY(qIsFinite(Bounds::denorm_min()));
QVERIFY(!qIsInf(Bounds::denorm_min()));
QVERIFY(!qIsNaN(Bounds::denorm_min()));
if (overOptimised)
QEXPECT_FAIL("", "Over-optimised on QEMU", Continue);
if (overOptimized)
QEXPECT_FAIL("", "Over-optimized on ARM", Continue);
QCOMPARE(Bounds::denorm_min() / rose, zero);
QCOMPARE(qFpClassify(Bounds::denorm_min()), FP_SUBNORMAL);
}
QTEST_APPLESS_MAIN(tst_qfloat16)