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 <volker.hilsheimer@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2023-09-11 08:55:38 +02:00
parent 9468ef2cfb
commit 16433a4a6e
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
\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 <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
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

View File

@ -7,6 +7,7 @@
#include <QtCore/qprocessordetection.h>
#include <QtCore/qtconfigmacros.h>
#include <QtCore/qassert.h>
#ifdef __cplusplus
# 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_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
#ifndef __cplusplus

View File

@ -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<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
#else
QSKIP("This test requires 128-bit integer support enabled in the compiler.");