diff --git a/src/corelib/global/qfloat16.h b/src/corelib/global/qfloat16.h index a25fac2886..387735863e 100644 --- a/src/corelib/global/qfloat16.h +++ b/src/corelib/global/qfloat16.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2016 by Southwest Research Institute (R) ** Contact: http://www.qt-project.org/legal ** @@ -308,6 +308,63 @@ inline qfloat16::operator float() const noexcept return qAbs(static_cast(f)) <= 0.001f; } +/* + qHypot compatibility; see ../kernel/qmath.h +*/ +namespace QtPrivate { +template +struct QHypotType { using type = decltype(std::hypot(R(1), 1.0f)); }; +template +struct QHypotType { using type = decltype(std::hypot(1.0f, R(1))); }; +template <> struct QHypotType { using type = qfloat16; }; +} +// Avoid passing qfloat16 to std::hypot(), while ensuring return types +// consistent with the above: +template auto qHypot(F first, Fs... rest); +template ::value, int>::type = 0> +auto qHypot(T x, qfloat16 y) { return qHypot(x, float(y)); } +template ::value, int>::type = 0> +auto qHypot(qfloat16 x, T y) { return qHypot(float(x), y); } +template <> inline auto qHypot(qfloat16 x, qfloat16 y) +{ +#if (defined(QT_COMPILER_SUPPORTS_F16C) && defined(__F16C__)) || defined (__ARM_FP16_FORMAT_IEEE) + return QtPrivate::QHypotHelper(x).add(y).result(); +#else + return qfloat16(qHypot(float(x), float(y))); +#endif +} +#if __cpp_lib_hypot >= 201603L // Expected to be true +// If any are not qfloat16, convert each qfloat16 to float: +/* (The following splits the some-but-not-all-qfloat16 cases up, using + (X|Y|Z)&~(X&Y&Z) = X ? ~(Y&Z) : Y|Z = X&~(Y&Z) | ~X&Y | ~X&~Y&Z, + into non-overlapping cases, to avoid ambiguity.) */ +template && std::is_same_v), int>::type = 0> +auto qHypot(qfloat16 x, Ty y, Tz z) { return qHypot(float(x), y, z); } +template , int>::type = 0> +auto qHypot(Tx x, qfloat16 y, Tz z) { return qHypot(x, float(y), z); } +template && !std::is_same_v, int>::type = 0> +auto qHypot(Tx x, Ty y, qfloat16 z) { return qHypot(x, y, float(z)); } +// If all are qfloat16, stay with qfloat16 (albeit via float, if no native support): +template <> +inline auto qHypot(qfloat16 x, qfloat16 y, qfloat16 z) +{ +#if (defined(QT_COMPILER_SUPPORTS_F16C) && defined(__F16C__)) || defined (__ARM_FP16_FORMAT_IEEE) + return QtPrivate::QHypotHelper(x).add(y).add(z).result(); +#else + return qfloat16(qHypot(float(x), float(y), float(z))); +#endif +} +#endif // 3-arg std::hypot() is available + QT_END_NAMESPACE Q_DECLARE_METATYPE(qfloat16) diff --git a/src/corelib/kernel/qmath.h b/src/corelib/kernel/qmath.h index 176e7d7978..298bb630fb 100644 --- a/src/corelib/kernel/qmath.h +++ b/src/corelib/kernel/qmath.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -135,6 +135,72 @@ template auto qSqrt(T v) return sqrt(v); } +namespace QtPrivate { +template // For qfloat16 to specialize +struct QHypotType { using type = decltype(std::hypot(R(1), F(1))); }; + +// Implements hypot() without limiting number of arguments: +template +class QHypotHelper +{ + const T scale, total; + template friend class QHypotHelper; + QHypotHelper(T first, T prior) : scale(first), total(prior) {} +public: + QHypotHelper(T first) : scale(qAbs(first)), total(1) {} + T result() const + { return qIsFinite(scale) ? scale > 0 ? scale * T(std::sqrt(total)) : T(0) : scale; } + + template + auto add(F first, Fs... rest) const + { return add(first).add(rest...); } + + template::type> + QHypotHelper add(F next) const + { + if (qIsInf(scale) || (qIsNaN(scale) && !qIsInf(next))) + return QHypotHelper(scale, R(1)); + if (qIsNaN(next)) + return QHypotHelper(next, R(1)); + const R val = qAbs(next); + if (!(scale > 0) || qIsInf(next)) + return QHypotHelper(val, R(1)); + if (!(val > 0)) + return QHypotHelper(scale, total); + if (val > scale) { + const R ratio = scale / next; + return QHypotHelper(val, total * ratio * ratio + 1); + } + const R ratio = next / scale; + return QHypotHelper(scale, total + ratio * ratio); + } +}; +} // QtPrivate + +template +auto qHypot(F first, Fs... rest) +{ + return QtPrivate::QHypotHelper(first).add(rest...).result(); +} + +// However, where possible, use the standard library implementations: +template +auto qHypot(Tx x, Ty y) +{ + // C99 has hypot(), hence C++11 has std::hypot() + using std::hypot; + return hypot(x, y); +} + +#if __cpp_lib_hypot >= 201603L // Expected to be true +template +auto qHypot(Tx x, Ty y, Tz z) +{ + using std::hypot; + return hypot(x, y, z); +} +#endif // else: no need to over-ride the arbitrarily-many-arg form + template auto qLn(T v) { using std::log; diff --git a/src/corelib/kernel/qmath.qdoc b/src/corelib/kernel/qmath.qdoc index b0a52a4a52..13655bcc11 100644 --- a/src/corelib/kernel/qmath.qdoc +++ b/src/corelib/kernel/qmath.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -139,7 +139,7 @@ This function will return the angle (argument) of that point. \relates - \sa qAtan() + \sa qAtan(), qHypot() */ /*! @@ -148,7 +148,57 @@ This function returns a NaN if \a v is a negative number. \relates - \sa qPow() + \sa qPow(), qHypot() +*/ + +/*! + \since 6.1 + \overload + \fn template auto qHypot(Tx x, Ty y) + Returns the distance of a point (x, y) from the origin (0, 0). + + This is qSqrt(x * x + y * y), optimized. + In particular, underflow and overflow may be avoided. + + Accepts any mix of numeric types, returning the same + floating-point type as std::hypot(). If either parameter is + infinite, so is the result; otherwise, if either is a NaN, so is + the result. + + \relates + \sa qSqrt(), qAtan2() +*/ + +/*! + \since 6.1 + \overload + \fn template auto qHypot(Tx x, Ty y, Tz z) + Returns the distance of a point (x, y, z) from the origin (0, 0, 0). + + This is qSqrt(x * x + y * y + z * z), optimized where supported. + In particular, underflow and overflow may be avoided. + + Accepts any mix of numeric types, returning the same + floating-point type as std::hypot(). If any parameter is infinite, + so is the result; otherwise, if any is NaN, so is the result. + + \relates + \sa qSqrt() +*/ + +/*! + \since 6.1 + \fn template auto qHypot(F first, Fs... rest) + Returns the distance from origin in arbitrarily many dimensions + + This is as for the two-argument and three-argument forms, supported by + std::hypot(), but with as many numeric parameters as you care to pass to + it. Uses \a first and each of the \a rest as co-ordinates, performing a + calculation equivalent to squaring each, summing and returning the square + root, save that underflow and overflow are avoided as far as possible. + + \relates + \sa qSqrt() */ /*! diff --git a/src/corelib/tools/qline.cpp b/src/corelib/tools/qline.cpp index 1eb645501c..dc0a6c72fb 100644 --- a/src/corelib/tools/qline.cpp +++ b/src/corelib/tools/qline.cpp @@ -572,8 +572,7 @@ QDataStream &operator>>(QDataStream &stream, QLine &line) */ qreal QLineF::length() const { - using std::hypot; - return hypot(dx(), dy()); + return qHypot(dx(), dy()); } /*! @@ -651,12 +650,11 @@ QLineF QLineF::fromPolar(qreal length, qreal angle) */ QLineF QLineF::unitVector() const { - qreal x = dx(); - qreal y = dy(); - using std::hypot; - qreal len = hypot(x, y); + const qreal x = dx(); + const qreal y = dy(); - QLineF f(p1(), QPointF(pt1.x() + x/len, pt1.y() + y/len)); + const qreal len = qHypot(x, y); + QLineF f(p1(), QPointF(pt1.x() + x / len, pt1.y() + y / len)); #ifndef QT_NO_DEBUG if (qAbs(f.length() - 1) >= 0.001) diff --git a/src/gui/painting/qtriangulatingstroker_p.h b/src/gui/painting/qtriangulatingstroker_p.h index 2b0f08972b..9537311ec5 100644 --- a/src/gui/painting/qtriangulatingstroker_p.h +++ b/src/gui/painting/qtriangulatingstroker_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. @@ -51,6 +51,7 @@ // We mean it. // +#include #include #include #include @@ -134,18 +135,9 @@ private: inline void QTriangulatingStroker::normalVector(float x1, float y1, float x2, float y2, float *nx, float *ny) { - float dx = x2 - x1; - float dy = y2 - y1; - Q_ASSERT(dx != 0 || dy != 0); - - float pw; - - if (dx == 0) - pw = m_width / std::abs(dy); - else if (dy == 0) - pw = m_width / std::abs(dx); - else - pw = m_width / std::sqrt(dx*dx + dy*dy); + const float dx = x2 - x1; + const float dy = y2 - y1; + const float pw = m_width / qHypot(dx, dy); *nx = -dy * pw; *ny = dx * pw; diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index 07f66af9e3..f69655e403 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -43,11 +43,11 @@ #include "qxcbscreen.h" #include "qxcbwindow.h" #include "QtCore/qmetaobject.h" +#include "QtCore/qmath.h" #include #include #include #include -#include #include @@ -758,11 +758,11 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo } else if (vci.label == QXcbAtom::AbsMTTouchMajor) { const qreal sw = screen->geometry().width(); const qreal sh = screen->geometry().height(); - w = valuatorNormalized * std::sqrt(sw * sw + sh * sh); + w = valuatorNormalized * qHypot(sw, sh); } else if (vci.label == QXcbAtom::AbsMTTouchMinor) { const qreal sw = screen->geometry().width(); const qreal sh = screen->geometry().height(); - h = valuatorNormalized * std::sqrt(sw * sw + sh * sh); + h = valuatorNormalized * qHypot(sw, sh); } else if (vci.label == QXcbAtom::AbsMTOrientation) { // Find the closest axis. // 0 corresponds to the Y axis, vci.max to the X axis. diff --git a/tests/auto/corelib/kernel/qmath/tst_qmath.cpp b/tests/auto/corelib/kernel/qmath/tst_qmath.cpp index 5496daf269..a94b984d47 100644 --- a/tests/auto/corelib/kernel/qmath/tst_qmath.cpp +++ b/tests/auto/corelib/kernel/qmath/tst_qmath.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2013 Laszlo Papp ** Contact: https://www.qt.io/licensing/ ** @@ -29,6 +29,7 @@ #include #include +#include class tst_QMath : public QObject { @@ -41,6 +42,7 @@ private slots: void radiansToDegrees(); void trigonometry_data(); void trigonometry(); + void hypotenuse(); void funcs_data(); void funcs(); void qNextPowerOfTwo32S_data(); @@ -152,7 +154,7 @@ void tst_QMath::trigonometry() QFETCH(const double, x); QFETCH(const double, y); QFETCH(const double, angle); - const double hypot = std::hypot(x, y); + const double hypot = qHypot(x, y); QVERIFY(hypot > 0); QCOMPARE(qAtan2(y, x), angle); QCOMPARE(qSin(angle), y / hypot); @@ -167,6 +169,79 @@ void tst_QMath::trigonometry() } } +void tst_QMath::hypotenuse() +{ + // Correct return-types, particularly when qfloat16 is involved: + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + + // Works for all numeric types: + QCOMPARE(qHypot(3, 4), 5); + QCOMPARE(qHypot(qfloat16(5), qfloat16(12)), qfloat16(13)); + QCOMPARE(qHypot(3.0f, 4.0f, 12.0f), 13.0f); + QCOMPARE(qHypot(3.0, 4.0, 12.0, 84.0), 85.0); + QCOMPARE(qHypot(3.0f, 4.0f, 12.0f, 84.0f, 720.0f), 725.0f); + QCOMPARE(qHypot(3.0, 4.0, 12.0, 84.0, 3612.0), 3613.0); + // Integral gets promoted to double: + QCOMPARE(qHypot(1, 1), M_SQRT2); + // Caller can mix types freely: + QCOMPARE(qHypot(3.0f, 4, 12.0, 84.0f, qfloat16(720), 10500), 10525); + // NaN wins over any finite: + QCOMPARE(qHypot(3, 4.0, 12.0f, qQNaN()), qQNaN()); + QCOMPARE(qHypot(3, 4.0, qQNaN(), 12.0f), qQNaN()); + QCOMPARE(qHypot(3, qQNaN(), 4.0, 12.0f), qQNaN()); + QCOMPARE(qHypot(qQNaN(), 3, 4.0, 12.0f), qQNaN()); + // but Infinity beats NaN: + QCOMPARE(qHypot(3, 4.0f, -qInf(), qQNaN()), qInf()); + QCOMPARE(qHypot(3, -qInf(), 4.0f, qQNaN()), qInf()); + QCOMPARE(qHypot(-qInf(), 3, 4.0f, qQNaN()), qInf()); + QCOMPARE(qHypot(qQNaN(), 3, -qInf(), 4.0f), qInf()); + QCOMPARE(qHypot(3, qQNaN(), 4.0f, -qInf()), qInf()); + QCOMPARE(qHypot(3, 4.0f, qQNaN(), -qInf()), qInf()); + // Components whose squares sum to zero don't change the end result: + const double minD = std::numeric_limits::min(); + QVERIFY(minD * minD + minD * minD == 0); // *NOT* QCOMPARE + QCOMPARE(qHypot(minD, minD, 12.0), 12.0); + const float minF = std::numeric_limits::min(); + QVERIFY(minF * minF + minF * minF == 0.0f); // *NOT* QCOMPARE + QCOMPARE(qHypot(minF, minF, 12.0f), 12.0f); + const qfloat16 minF16 = std::numeric_limits::min(); + QVERIFY(minF16 * minF16 + minF16 * minF16 == qfloat16(0)); // *NOT* QCOMPARE + QCOMPARE(qHypot(minF16, minF16, qfloat16(12)), qfloat16(12)); +} + void tst_QMath::funcs_data() { QTest::addColumn("value");