| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2020 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include <QtCore/QCoreApplication> |
| 30 | #include <QtCore/qfloat16.h> |
| 31 | #include <QtTest/QtTest> |
| 32 | #include <QDebug> |
| 33 | |
| 34 | // Test proper handling of floating-point types |
| 35 | class tst_float: public QObject |
| 36 | { |
| 37 | Q_OBJECT |
| 38 | private slots: |
| 39 | void doubleComparisons() const; |
| 40 | void doubleComparisons_data() const; |
| 41 | void floatComparisons() const; |
| 42 | void floatComparisons_data() const; |
| 43 | void float16Comparisons() const; |
| 44 | void float16Comparisons_data() const; |
| 45 | void compareFloatTests() const; |
| 46 | void compareFloatTests_data() const; |
| 47 | }; |
| 48 | |
| 49 | template<typename F> |
| 50 | static void nonFinite_data(F zero, F one) |
| 51 | { |
| 52 | using Bounds = std::numeric_limits<F>; |
| 53 | |
| 54 | // QCOMPARE special-cases non-finite values |
| 55 | if (Bounds::has_quiet_NaN) { |
| 56 | const F nan = Bounds::quiet_NaN(); |
| 57 | QTest::newRow(dataTag: "should PASS: NaN == NaN" ) << nan << nan; |
| 58 | QTest::newRow(dataTag: "should FAIL: NaN != 0" ) << nan << zero; |
| 59 | QTest::newRow(dataTag: "should FAIL: 0 != NaN" ) << zero << nan; |
| 60 | QTest::newRow(dataTag: "should FAIL: NaN != 1" ) << nan << one; |
| 61 | QTest::newRow(dataTag: "should FAIL: 1 != NaN" ) << one << nan; |
| 62 | } |
| 63 | |
| 64 | if (Bounds::has_infinity) { |
| 65 | const F uge = Bounds::infinity(); |
| 66 | QTest::newRow(dataTag: "should PASS: inf == inf" ) << uge << uge; |
| 67 | QTest::newRow(dataTag: "should PASS: -inf == -inf" ) << -uge << -uge; |
| 68 | QTest::newRow(dataTag: "should FAIL: inf != -inf" ) << uge << -uge; |
| 69 | QTest::newRow(dataTag: "should FAIL: -inf != inf" ) << -uge << uge; |
| 70 | if (Bounds::has_quiet_NaN) { |
| 71 | const F nan = Bounds::quiet_NaN(); |
| 72 | QTest::newRow(dataTag: "should FAIL: inf != nan" ) << uge << nan; |
| 73 | QTest::newRow(dataTag: "should FAIL: nan != inf" ) << nan << uge; |
| 74 | QTest::newRow(dataTag: "should FAIL: -inf != nan" ) << -uge << nan; |
| 75 | QTest::newRow(dataTag: "should FAIL: nan != -inf" ) << nan << -uge; |
| 76 | } |
| 77 | QTest::newRow(dataTag: "should FAIL: inf != 0" ) << uge << zero; |
| 78 | QTest::newRow(dataTag: "should FAIL: 0 != inf" ) << zero << uge; |
| 79 | QTest::newRow(dataTag: "should FAIL: -inf != 0" ) << -uge << zero; |
| 80 | QTest::newRow(dataTag: "should FAIL: 0 != -inf" ) << zero << -uge; |
| 81 | QTest::newRow(dataTag: "should FAIL: inf != 1" ) << uge << one; |
| 82 | QTest::newRow(dataTag: "should FAIL: 1 != inf" ) << one << uge; |
| 83 | QTest::newRow(dataTag: "should FAIL: -inf != 1" ) << -uge << one; |
| 84 | QTest::newRow(dataTag: "should FAIL: 1 != -inf" ) << one << -uge; |
| 85 | |
| 86 | const F big = Bounds::max(); |
| 87 | QTest::newRow(dataTag: "should FAIL: inf != max" ) << uge << big; |
| 88 | QTest::newRow(dataTag: "should FAIL: inf != -max" ) << uge << -big; |
| 89 | QTest::newRow(dataTag: "should FAIL: max != inf" ) << big << uge; |
| 90 | QTest::newRow(dataTag: "should FAIL: -max != inf" ) << -big << uge; |
| 91 | QTest::newRow(dataTag: "should FAIL: -inf != max" ) << -uge << big; |
| 92 | QTest::newRow(dataTag: "should FAIL: -inf != -max" ) << -uge << -big; |
| 93 | QTest::newRow(dataTag: "should FAIL: max != -inf" ) << big << -uge; |
| 94 | QTest::newRow(dataTag: "should FAIL: -max != -inf" ) << -big << -uge; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | void tst_float::doubleComparisons() const |
| 99 | { |
| 100 | QFETCH(double, operandLeft); |
| 101 | QFETCH(double, operandRight); |
| 102 | |
| 103 | QCOMPARE(operandLeft, operandRight); |
| 104 | } |
| 105 | |
| 106 | void tst_float::doubleComparisons_data() const |
| 107 | { |
| 108 | QTest::addColumn<double>(name: "operandLeft" ); |
| 109 | QTest::addColumn<double>(name: "operandRight" ); |
| 110 | double zero(0.), one(1.); |
| 111 | |
| 112 | QTest::newRow(dataTag: "should FAIL 1" ) << one << 3.; |
| 113 | QTest::newRow(dataTag: "should PASS 1" ) << zero << zero; |
| 114 | QTest::newRow(dataTag: "should FAIL 2" ) << 1.e-7 << 3.e-7; |
| 115 | |
| 116 | // QCOMPARE() uses qFuzzyCompare(), which succeeds if doubles differ by no |
| 117 | // more than 1e-12 times the smaller value; but QCOMPARE() also considers |
| 118 | // values equal if qFuzzyIsNull() is true for both, so all doubles smaller |
| 119 | // than 1e-12 are equal. Thus QCOMPARE(1e12-2, 1e12-1) should fail, while |
| 120 | // QCOMPARE(1e12+1, 1e12+2) should pass, as should QCOMPARE(1e-12-2e-24, |
| 121 | // 1e-12-1e-24), despite the values differing by more than one part in 1e12. |
| 122 | |
| 123 | QTest::newRow(dataTag: "should PASS 2" ) << 1e12 + one << 1e12 + 2.; |
| 124 | QTest::newRow(dataTag: "should FAIL 3" ) << 1e12 - one << 1e12 - 2.; |
| 125 | QTest::newRow(dataTag: "should PASS 3" ) << 1e-12 << -1e-12; |
| 126 | // ... but rounding makes that a bit unrelaible when scaled close to the bounds. |
| 127 | QTest::newRow(dataTag: "should FAIL 4" ) << 1e-12 + 1e-24 << 1e-12 - 1e-24; |
| 128 | QTest::newRow(dataTag: "should PASS 4" ) << 1e307 + 1e295 << 1e307 + 2e295; |
| 129 | QTest::newRow(dataTag: "should FAIL 5" ) << 1e307 - 1e295 << 1e307 - 3e295; |
| 130 | |
| 131 | nonFinite_data(zero, one); |
| 132 | } |
| 133 | |
| 134 | void tst_float::floatComparisons() const |
| 135 | { |
| 136 | QFETCH(float, operandLeft); |
| 137 | QFETCH(float, operandRight); |
| 138 | |
| 139 | QCOMPARE(operandLeft, operandRight); |
| 140 | } |
| 141 | |
| 142 | void tst_float::floatComparisons_data() const |
| 143 | { |
| 144 | QTest::addColumn<float>(name: "operandLeft" ); |
| 145 | QTest::addColumn<float>(name: "operandRight" ); |
| 146 | float zero(0.f), one(1.f); |
| 147 | |
| 148 | QTest::newRow(dataTag: "should FAIL 1" ) << one << 3.f; |
| 149 | QTest::newRow(dataTag: "should PASS 1" ) << zero << zero; |
| 150 | QTest::newRow(dataTag: "should FAIL 2" ) << 1.e-5f << 3.e-5f; |
| 151 | |
| 152 | // QCOMPARE() uses qFuzzyCompare(), which succeeds if the floats differ by |
| 153 | // no more than 1e-5 times the smaller value; but QCOMPARE() also considers |
| 154 | // values equal if qFuzzyIsNull is true for both, so all floats smaller than |
| 155 | // 1e-5 are equal. Thus QCOMPARE(1e5-2, 1e5-1) should fail, while |
| 156 | // QCOMPARE(1e5+1, 1e5+2) should pass, as should QCOMPARE(1e-5-2e-10, |
| 157 | // 1e-5-1e-10), despite the values differing by more than one part in 1e5. |
| 158 | |
| 159 | QTest::newRow(dataTag: "should PASS 2" ) << 1e5f + one << 1e5f + 2.f; |
| 160 | QTest::newRow(dataTag: "should FAIL 3" ) << 1e5f - one << 1e5f - 2.f; |
| 161 | QTest::newRow(dataTag: "should PASS 3" ) << 1e-5f << -1e-5f; |
| 162 | // ... but rounding makes that a bit unrelaible when scaled close to the bounds. |
| 163 | QTest::newRow(dataTag: "should FAIL 4" ) << 1e-5f + 1e-10f << 1e-5f - 1e-10f; |
| 164 | QTest::newRow(dataTag: "should PASS 4" ) << 1e38f + 1e33f << 1e38f + 2e33f; |
| 165 | QTest::newRow(dataTag: "should FAIL 5" ) << 1e38f - 1e33f << 1e38f - 3e33f; |
| 166 | |
| 167 | nonFinite_data(zero, one); |
| 168 | } |
| 169 | |
| 170 | void tst_float::float16Comparisons() const |
| 171 | { |
| 172 | QFETCH(qfloat16, operandLeft); |
| 173 | QFETCH(qfloat16, operandRight); |
| 174 | |
| 175 | QCOMPARE(operandLeft, operandRight); |
| 176 | } |
| 177 | |
| 178 | void tst_float::float16Comparisons_data() const |
| 179 | { |
| 180 | QTest::addColumn<qfloat16>(name: "operandLeft" ); |
| 181 | QTest::addColumn<qfloat16>(name: "operandRight" ); |
| 182 | const qfloat16 zero(0), one(1); |
| 183 | const qfloat16 tiny(0.00099f); |
| 184 | |
| 185 | QTest::newRow(dataTag: "should FAIL 1" ) << one << qfloat16(3); |
| 186 | QTest::newRow(dataTag: "should PASS 1" ) << zero << zero; |
| 187 | QTest::newRow(dataTag: "should FAIL 2" ) << qfloat16(1.f/128.f) << qfloat16(3e-3f); |
| 188 | |
| 189 | // QCOMPARE for uses qFuzzyCompare(), which ignores differences of one part |
| 190 | // in 102.5 and considers any two qFuzzyIsNull() values, i.e. values smaller |
| 191 | // than 1e-3, equal |
| 192 | QTest::newRow(dataTag: "should PASS 2" ) << qfloat16(1001) << qfloat16(1002); |
| 193 | QTest::newRow(dataTag: "should FAIL 3" ) << qfloat16(98) << qfloat16(99); |
| 194 | QTest::newRow(dataTag: "should PASS 3" ) << tiny << -tiny; |
| 195 | // ... which gets a bit unreliable near to the type's bounds |
| 196 | QTest::newRow(dataTag: "should FAIL 4" ) << qfloat16(1.01e-3f) << qfloat16(0.99e-3f); |
| 197 | QTest::newRow(dataTag: "should PASS 4" ) << qfloat16(6e4) + qfloat16(700) << qfloat16(6e4) + qfloat16(1200); |
| 198 | QTest::newRow(dataTag: "should FAIL 5" ) << qfloat16(6e4) - qfloat16(600) << qfloat16(6e4) - qfloat16(1200); |
| 199 | |
| 200 | nonFinite_data(zero, one); |
| 201 | } |
| 202 | |
| 203 | void tst_float::compareFloatTests() const |
| 204 | { |
| 205 | QFETCH(float, t1); |
| 206 | |
| 207 | // Create two more values |
| 208 | // t2 differs from t1 by 1 ppm (part per million) |
| 209 | // t3 differs from t1 by 200% |
| 210 | // We should consider that t1 == t2 and t1 != t3 (provided at least one is > 1e-5) |
| 211 | const float t2 = t1 + (t1 / 1e6); |
| 212 | const float t3 = 3 * t1; |
| 213 | |
| 214 | QCOMPARE(t1, t2); |
| 215 | |
| 216 | /* Should FAIL. */ |
| 217 | QCOMPARE(t1, t3); |
| 218 | } |
| 219 | |
| 220 | void tst_float::compareFloatTests_data() const |
| 221 | { |
| 222 | QTest::addColumn<float>(name: "t1" ); |
| 223 | QTest::newRow(dataTag: "1e0" ) << 1e0f; |
| 224 | QTest::newRow(dataTag: "1e-5" ) << 1e-5f; |
| 225 | QTest::newRow(dataTag: "1e+7" ) << 1e+7f; |
| 226 | } |
| 227 | |
| 228 | QTEST_MAIN(tst_float) |
| 229 | |
| 230 | #include "tst_float.moc" |
| 231 | |