From 16433a4a6ed22750adfdb9633149c3bd485c4656 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Mon, 11 Sep 2023 08:55:38 +0200 Subject: Long live Q_(U)INT128_C()! Compilers that support 128-bit integer types usually don't have support for 128-bit literals, so provide Q_(U)INT128_C macros and back them with UDLs. This, of course, only works in C++, so until compilers provide built-in literals that support C, too, that's all we get. [ChangeLog][QtCore] Added Q_INT128_C() and Q_UINT128_C() macros to create qint128 and quint128 literals in a platform-independent way. Pick-to: 6.6 6.6.0 Fixes: QTBUG-116822 Change-Id: I4be645baf2e007ee1aa1a27f9b5166671806dc49 Reviewed-by: Volker Hilsheimer Reviewed-by: Qt CI Bot Reviewed-by: Thiago Macieira --- src/corelib/global/qtypes.cpp | 50 +++++++-- src/corelib/global/qtypes.h | 72 ++++++++++++ tests/auto/corelib/global/qglobal/tst_qglobal.cpp | 129 ++++++++++++++++++++++ 3 files changed, 243 insertions(+), 8 deletions(-) diff --git a/src/corelib/global/qtypes.cpp b/src/corelib/global/qtypes.cpp index 507fd6bbde..19a7541ed1 100644 --- a/src/corelib/global/qtypes.cpp +++ b/src/corelib/global/qtypes.cpp @@ -152,7 +152,9 @@ QT_BEGIN_NAMESPACE Typedef for \c{__int128} on platforms that support it (Qt defines the macro \l QT_SUPPORTS_INT128 if this is the case). - \sa Q_INT128_MIN, Q_INT128_MAX, quint128, QT_SUPPORTS_INT128 + Literals of this type can be created using the Q_INT128_C() macro. + + \sa Q_INT128_C(), Q_INT128_MIN, Q_INT128_MAX, quint128, QT_SUPPORTS_INT128 */ /*! @@ -163,7 +165,9 @@ QT_BEGIN_NAMESPACE Typedef for \c{unsigned __int128} on platforms that support it (Qt defines the macro \l QT_SUPPORTS_INT128 if this is the case). - \sa Q_UINT128_MAX, qint128, QT_SUPPORTS_INT128 + Literals of this type can be created using the Q_UINT128_C() macro. + + \sa Q_UINT128_C(), Q_UINT128_MAX, qint128, QT_SUPPORTS_INT128 */ /*! @@ -174,7 +178,7 @@ QT_BEGIN_NAMESPACE Qt defines this macro as well as the \l qint128 and \l quint128 types if the platform has support for 128-bit integer types. - \sa qint128, quint128, Q_INT128_MIN, Q_INT128_MAX, Q_UINT128_MAX + \sa qint128, quint128, Q_INT128_C(), Q_UINT128_C(), Q_INT128_MIN, Q_INT128_MAX, Q_UINT128_MAX */ /*! @@ -360,7 +364,7 @@ QT_BEGIN_NAMESPACE \snippet code/src_corelib_global_qglobal.cpp 8 - \sa qint64, Q_UINT64_C() + \sa qint64, Q_UINT64_C(), Q_INT128_C() */ /*! \macro quint64 Q_UINT64_C(literal) @@ -373,7 +377,37 @@ QT_BEGIN_NAMESPACE \snippet code/src_corelib_global_qglobal.cpp 9 - \sa quint64, Q_INT64_C() + \sa quint64, Q_INT64_C(), Q_UINT128_C() +*/ + +/*! + \macro qint128 Q_INT128_C(literal) + \relates + \since 6.6 + + Wraps the signed 128-bit integer \a literal in a + platform-independent way. + + \note Unlike Q_INT64_C(), this macro is only available in C++, not in C. + This is because compilers do not provide these literals as built-ins and C + does not have support for user-defined literals. + + \sa qint128, Q_UINT128_C(), Q_INT128_MIN, Q_INT128_MAX, Q_INT64_C(), QT_SUPPORTS_INT128 +*/ + +/*! + \macro quint128 Q_UINT128_C(literal) + \relates + \since 6.6 + + Wraps the unsigned 128-bit integer \a literal in a + platform-independent way. + + \note Unlike Q_UINT64_C(), this macro is only available in C++, not in C. + This is because compilers do not provide these literals as built-ins and C + does not have support for user-defined literals. + + \sa quint128, Q_INT128_C(), Q_UINT128_MAX, Q_UINT64_C(), QT_SUPPORTS_INT128 */ /*! @@ -389,7 +423,7 @@ QT_BEGIN_NAMESPACE The minimum of \l quint128 is 0 (zero), so a \c{Q_UINT128_MIN} is neither needed nor provided. - \sa Q_INT128_MAX, quint128, QT_SUPPORTS_INT128 + \sa Q_INT128_MAX, quint128, Q_UINT128_C, QT_SUPPORTS_INT128 */ /*! @@ -402,7 +436,7 @@ QT_BEGIN_NAMESPACE This macro is available in both C++ and C modes. - \sa Q_INT128_MAX, qint128, QT_SUPPORTS_INT128 + \sa Q_INT128_MAX, qint128, Q_INT128_C, QT_SUPPORTS_INT128 */ /*! @@ -415,7 +449,7 @@ QT_BEGIN_NAMESPACE This macro is available in both C++ and C modes. - \sa Q_INT128_MIN, Q_UINT128_MAX, qint128, QT_SUPPORTS_INT128 + \sa Q_INT128_MIN, Q_UINT128_MAX, qint128, Q_INT128_C, QT_SUPPORTS_INT128 */ // Statically check assumptions about the environment we're running diff --git a/src/corelib/global/qtypes.h b/src/corelib/global/qtypes.h index cca2263901..41e6025735 100644 --- a/src/corelib/global/qtypes.h +++ b/src/corelib/global/qtypes.h @@ -7,6 +7,7 @@ #include #include +#include #ifdef __cplusplus # include @@ -80,6 +81,77 @@ __extension__ typedef __uint128_t quint128; # define Q_INT128_MAX QT_C_STYLE_CAST(qint128, (Q_UINT128_MAX / 2)) # define Q_INT128_MIN (-Q_INT128_MAX - 1) +# ifdef __cplusplus + namespace QtPrivate::NumberLiterals { + namespace detail { + template + constexpr quint128 construct() { return accu; } + + template + constexpr quint128 construct() + { + if constexpr (C != '\'') { // ignore digit separators + const int digitValue = '0' <= C && C <= '9' ? C - '0' : + 'a' <= C && C <= 'z' ? C - 'a' + 10 : + 'A' <= C && C <= 'Z' ? C - 'A' + 10 : + /* else */ -1 ; + static_assert(digitValue >= 0 && digitValue < base, + "Invalid character"); + // accu * base + digitValue <= MAX, but without overflow: + static_assert(accu <= (Q_UINT128_MAX - digitValue) / base, + "Overflow occurred"); + return construct(); + } else { + return construct(); + } + } + + template + constexpr quint128 parse0xb() + { + constexpr quint128 accu = 0; + if constexpr (C == 'x' || C == 'X') + return construct(); // base 16, skip 'x' + else if constexpr (C == 'b' || C == 'B') + return construct(); // base 2, skip 'b' + else + return construct(); // base 8, include C + } + + template + constexpr quint128 parse0() + { + if constexpr (sizeof...(Cs) == 0) // this was just a literal 0 + return 0; + else + return parse0xb(); + } + + template + constexpr quint128 parse() + { + if constexpr (C == '0') + return parse0(); // base 2, 8, or 16 (or just a literal 0), skip '0' + else + return construct<0, 10, C, Cs...>(); // initial accu 0, base 10, include C + } + } // namespace detail + template + constexpr quint128 operator""_quint128() noexcept + { return QtPrivate::NumberLiterals::detail::parse(); } + template + constexpr qint128 operator""_qint128() noexcept + { return qint128(QtPrivate::NumberLiterals::detail::parse()); } + + #ifndef Q_UINT128_C // allow qcompilerdetection.h/user override + # define Q_UINT128_C(c) ([]{ using namespace QtPrivate::NumberLiterals; return c ## _quint128; }()) + #endif + #ifndef Q_INT128_C // allow qcompilerdetection.h/user override + # define Q_INT128_C(c) ([]{ using namespace QtPrivate::NumberLiterals; return c ## _qint128; }()) + #endif + + } // namespace QtPrivate::NumberLiterals +# endif // __cplusplus #endif // QT_SUPPORTS_INT128 #ifndef __cplusplus diff --git a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp index 638c43f98a..6da78555cb 100644 --- a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp +++ b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp @@ -499,6 +499,135 @@ void tst_QGlobal::int128Literals() QCOMPARE_EQ(tst_qint128_min(), Q_INT128_MIN); QCOMPARE_EQ(tst_qint128_max(), Q_INT128_MAX); QCOMPARE_EQ(tst_quint128_max(), Q_UINT128_MAX); + { + #define CHECK_S(x) COMPARE_EQ(Q_INT128_C(x), Q_INT64_C(x), qint128) + #define CHECK_U(x) COMPARE_EQ(Q_UINT128_C(x), Q_UINT64_C(x), quint128); + #define CHECK(x) do { CHECK_S(x); CHECK_U(x); } while (0) + // basics: + CHECK(0); + CHECK(1); + CHECK_S(-1); + QCOMPARE_EQ(Q_INT64_C(9223372036854775807), std::numeric_limits::max()); + CHECK(9223372036854775807); // LLONG_MAX + // Q_INT64_C(-9223372036854775808) gives -Wimplicitly-unsigned-literal on GCC, so use numeric_limits: + { + constexpr auto i = Q_INT128_C(-9223372036854775808); // LLONG_MIN + static_assert(std::is_same_v); + QCOMPARE_EQ(i, std::numeric_limits::min()); + } + // actual 128-bit numbers + { + constexpr auto i = Q_INT128_C( 9223372036854775808); // LLONG_MAX + 1 + constexpr auto u = Q_UINT128_C(9223372036854775808); // LLONG_MAX + 1 + static_assert(std::is_same_v); + static_assert(std::is_same_v); + QCOMPARE_EQ(i, qint128{ std::numeric_limits::max()} + 1); + QCOMPARE_EQ(u, quint128{std::numeric_limits::max()} + 1); + } + { + constexpr auto i = Q_INT128_C(-9223372036854775809); // LLONG_MIN - 1 + static_assert(std::is_same_v); + QCOMPARE_EQ(i, qint128{std::numeric_limits::min()} - 1); + } + { + constexpr auto i = Q_INT128_C( 18446744073709551616); // ULLONG_MAX + 1 + constexpr auto u = Q_UINT128_C(18446744073709551616); + constexpr auto expected = qint128{1} << 64; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + QCOMPARE_EQ(i, expected); + QCOMPARE_EQ(u, quint128{expected}); + } + { + // compilers don't let one write signed _MIN literals, so use MIN + 1: + // Q_INT128_C(-170141183460469231731687303715884105728) gives + // ERROR: ~~~ outside range of representable values of type qint128 + // This is because the unary minus is technically speaking not part of + // the literal, but called on the result of the literal. + constexpr auto i = Q_INT128_C(-170141183460469231731687303715884105727); // 128-bit MIN + 1 + static_assert(std::is_same_v); + QCOMPARE_EQ(i, std::numeric_limits::min() + 1); + } + { + constexpr auto i = Q_INT128_C( 170141183460469231731687303715884105727); // MAX + constexpr auto u = Q_UINT128_C(340282366920938463463374607431768211455); // UMAX + static_assert(std::is_same_v); + static_assert(std::is_same_v); + QCOMPARE_EQ(i, std::numeric_limits::max()); + QCOMPARE_EQ(u, std::numeric_limits::max()); + QCOMPARE_EQ(u, Q_UINT128_C(-1)); + } + + // binary literals: + CHECK(0b0); + CHECK(0b1); + CHECK_S(-0b1); + CHECK(0b01); + CHECK(0b10); + CHECK(0b1'1); // with digit separator + CHECK(0b0111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111); + //bytes |---1---| |---2---| |---3---| |---4---| |---5---| |---6---| |---7---| |---8---| + { + // bytes: |---1---| |---2---| |---3---| |---4---| |---5---| |---6---| |---7---| |---8---| |---9---| |--10---| |--11---| |--12---| |--13---| |--14---| |--15---| |--16---| + constexpr auto i = Q_INT128_C( 0b0111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111); + constexpr auto u = Q_UINT128_C(0b1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + QCOMPARE_EQ(i, std::numeric_limits::max()); + QCOMPARE_EQ(u, std::numeric_limits::max()); + QCOMPARE_EQ(u, Q_UINT128_C(-0b1)); + } + + // octal literals: + CHECK(00); + CHECK(01); + CHECK(02); + CHECK(03); + CHECK(04); + CHECK(05); + CHECK(06); + CHECK(07); + CHECK_S(-01); + CHECK(010); + CHECK_S(-01'0); // with digit separator + CHECK(07'7777'7777'7777'7777'7777); // LLONG_MAX + { + // bits: 120| 108| 96| 84| 72| 60| 48| 36| 24| 12| 0| + constexpr auto i = Q_INT128_C( 0177'7777'7777'7777'7777'7777'7777'7777'7777'7777'7777); + constexpr auto u = Q_UINT128_C(0377'7777'7777'7777'7777'7777'7777'7777'7777'7777'7777); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + QCOMPARE_EQ(i, std::numeric_limits::max()); + QCOMPARE_EQ(u, std::numeric_limits::max()); + QCOMPARE_EQ(u, Q_UINT128_C(-01)); + } + + // hex literals: + CHECK(0x0); + CHECK(0x1); + CHECK(0x9); + CHECK(0xA); + CHECK(0xB); + CHECK(0xC); + CHECK(0xD); + CHECK(0xE); + CHECK(0x0F); + CHECK(0x10); + CHECK_S(-0x1); + CHECK_S(-0x1'0); // with digit separator + CHECK(0x7FFF'FFFF'FFFF'FFFF); + { + constexpr auto i = Q_INT128_C( 0x7FFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFF); + constexpr auto u = Q_UINT128_C(0xFFFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFF); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + QCOMPARE_EQ(i, std::numeric_limits::max()); + QCOMPARE_EQ(u, std::numeric_limits::max()); + QCOMPARE_EQ(Q_UINT128_C(-1), u); + } + #undef CHECK + } #undef COMPARE_EQ #else QSKIP("This test requires 128-bit integer support enabled in the compiler."); -- cgit v1.2.3