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.

Fixes: QTBUG-116822
Change-Id: I4be645baf2e007ee1aa1a27f9b5166671806dc49
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 16433a4a6ed22750adfdb9633149c3bd485c4656)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 321dcd1d1d0fdd627160e882c471529baa6cafa7)
This commit is contained in:
Marc Mutz 2023-09-11 08:55:38 +02:00 committed by Qt Cherry-pick Bot
parent a1a5e12ef9
commit e8514f771d
3 changed files with 243 additions and 8 deletions

View File

@ -152,7 +152,9 @@ QT_BEGIN_NAMESPACE
Typedef for \c{__int128} on platforms that support it (Qt defines the macro Typedef for \c{__int128} on platforms that support it (Qt defines the macro
\l QT_SUPPORTS_INT128 if this is the case). \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 Typedef for \c{unsigned __int128} on platforms that support it (Qt defines
the macro \l QT_SUPPORTS_INT128 if this is the case). 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 Qt defines this macro as well as the \l qint128 and \l quint128 types if
the platform has support for 128-bit integer types. 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 \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) /*! \macro quint64 Q_UINT64_C(literal)
@ -373,7 +377,37 @@ QT_BEGIN_NAMESPACE
\snippet code/src_corelib_global_qglobal.cpp 9 \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 <QtTypes>
\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 <QtTypes>
\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 The minimum of \l quint128 is 0 (zero), so a \c{Q_UINT128_MIN} is neither
needed nor provided. 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. 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. 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 // Statically check assumptions about the environment we're running

View File

@ -7,6 +7,7 @@
#include <QtCore/qprocessordetection.h> #include <QtCore/qprocessordetection.h>
#include <QtCore/qtconfigmacros.h> #include <QtCore/qtconfigmacros.h>
#include <QtCore/qassert.h>
#ifdef __cplusplus #ifdef __cplusplus
# include <cstddef> # include <cstddef>
@ -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_MAX QT_C_STYLE_CAST(qint128, (Q_UINT128_MAX / 2))
# define Q_INT128_MIN (-Q_INT128_MAX - 1) # define Q_INT128_MIN (-Q_INT128_MAX - 1)
# ifdef __cplusplus
namespace QtPrivate::NumberLiterals {
namespace detail {
template <quint128 accu, int base>
constexpr quint128 construct() { return accu; }
template <quint128 accu, int base, char C, char...Cs>
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<accu * base + digitValue, base, Cs...>();
} else {
return construct<accu, base, Cs...>();
}
}
template <char C, char...Cs>
constexpr quint128 parse0xb()
{
constexpr quint128 accu = 0;
if constexpr (C == 'x' || C == 'X')
return construct<accu, 16, Cs...>(); // base 16, skip 'x'
else if constexpr (C == 'b' || C == 'B')
return construct<accu, 2, Cs...>(); // base 2, skip 'b'
else
return construct<accu, 8, C, Cs...>(); // base 8, include C
}
template <char...Cs>
constexpr quint128 parse0()
{
if constexpr (sizeof...(Cs) == 0) // this was just a literal 0
return 0;
else
return parse0xb<Cs...>();
}
template <char C, char...Cs>
constexpr quint128 parse()
{
if constexpr (C == '0')
return parse0<Cs...>(); // 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 <char...Cs>
constexpr quint128 operator""_quint128() noexcept
{ return QtPrivate::NumberLiterals::detail::parse<Cs...>(); }
template <char...Cs>
constexpr qint128 operator""_qint128() noexcept
{ return qint128(QtPrivate::NumberLiterals::detail::parse<Cs...>()); }
#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 #endif // QT_SUPPORTS_INT128
#ifndef __cplusplus #ifndef __cplusplus

View File

@ -497,6 +497,135 @@ void tst_QGlobal::int128Literals()
QCOMPARE_EQ(tst_qint128_min(), Q_INT128_MIN); QCOMPARE_EQ(tst_qint128_min(), Q_INT128_MIN);
QCOMPARE_EQ(tst_qint128_max(), Q_INT128_MAX); QCOMPARE_EQ(tst_qint128_max(), Q_INT128_MAX);
QCOMPARE_EQ(tst_quint128_max(), Q_UINT128_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<qint64>::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<decltype(i), const qint128>);
QCOMPARE_EQ(i, std::numeric_limits<qint64>::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<decltype(i), const qint128>);
static_assert(std::is_same_v<decltype(u), const quint128>);
QCOMPARE_EQ(i, qint128{ std::numeric_limits<qint64>::max()} + 1);
QCOMPARE_EQ(u, quint128{std::numeric_limits<qint64>::max()} + 1);
}
{
constexpr auto i = Q_INT128_C(-9223372036854775809); // LLONG_MIN - 1
static_assert(std::is_same_v<decltype(i), const qint128>);
QCOMPARE_EQ(i, qint128{std::numeric_limits<qint64>::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<decltype(i), const qint128>);
static_assert(std::is_same_v<decltype(expected), const qint128>);
static_assert(std::is_same_v<decltype(u), const quint128>);
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<decltype(i), const qint128>);
QCOMPARE_EQ(i, std::numeric_limits<qint128>::min() + 1);
}
{
constexpr auto i = Q_INT128_C( 170141183460469231731687303715884105727); // MAX
constexpr auto u = Q_UINT128_C(340282366920938463463374607431768211455); // UMAX
static_assert(std::is_same_v<decltype(i), const qint128>);
static_assert(std::is_same_v<decltype(u), const quint128>);
QCOMPARE_EQ(i, std::numeric_limits<qint128>::max());
QCOMPARE_EQ(u, std::numeric_limits<quint128>::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<decltype(i), const qint128>);
static_assert(std::is_same_v<decltype(u), const quint128>);
QCOMPARE_EQ(i, std::numeric_limits<qint128>::max());
QCOMPARE_EQ(u, std::numeric_limits<quint128>::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<decltype(i), const qint128>);
static_assert(std::is_same_v<decltype(u), const quint128>);
QCOMPARE_EQ(i, std::numeric_limits<qint128>::max());
QCOMPARE_EQ(u, std::numeric_limits<quint128>::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<decltype(i), const qint128>);
static_assert(std::is_same_v<decltype(u), const quint128>);
QCOMPARE_EQ(i, std::numeric_limits<qint128>::max());
QCOMPARE_EQ(u, std::numeric_limits<quint128>::max());
QCOMPARE_EQ(Q_UINT128_C(-1), u);
}
#undef CHECK
}
#undef COMPARE_EQ #undef COMPARE_EQ
#else #else
QSKIP("This test requires 128-bit integer support enabled in the compiler."); QSKIP("This test requires 128-bit integer support enabled in the compiler.");