From e4876f2ce92bd1e93a9305d6ce1b07cde9b26f51 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 22 Jul 2026 09:47:49 -0400 Subject: [PATCH 1/6] Add reproducer test set --- test/test_literals.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/test_literals.cpp b/test/test_literals.cpp index 2e61254d..f8d16c14 100644 --- a/test/test_literals.cpp +++ b/test/test_literals.cpp @@ -32,6 +32,24 @@ void test_u128_literals() BOOST_TEST(max_val == macro_val); } +void test_u128_digit_separators() +{ + // C++ digit separators (') are ignored inside numeric literals + BOOST_TEST(boost::int128::uint128_t{1234567} == 1'234'567_u128); + BOOST_TEST(boost::int128::uint128_t{1000} == 1'000_U128); + BOOST_TEST(boost::int128::uint128_t{1234} == 12'34_u128); + + // Separators are also honored through the convenience macro + BOOST_TEST(boost::int128::uint128_t{1234} == BOOST_INT128_UINT128_C(1'234)); + + // Full-width value with a separator between every group of three digits + const boost::int128::uint128_t max_val {std::numeric_limits::max()}; + BOOST_TEST(max_val == 340'282'366'920'938'463'463'374'607'431'768'211'455_u128); + + // Separators must be usable in a constant expression + static_assert(100'000_u128 == boost::int128::uint128_t{100000}, "constexpr separator"); +} + void test_i128_literals() { BOOST_TEST(boost::int128::int128_t{0} == 0_i128); @@ -49,10 +67,32 @@ void test_i128_literals() BOOST_TEST("-42"_i128 == -42); } +void test_i128_digit_separators() +{ + // C++ digit separators (') are ignored inside numeric literals + BOOST_TEST(boost::int128::int128_t{1000} == 1'000_i128); + BOOST_TEST(boost::int128::int128_t{9999} == 9'999_I128); + + // A leading unary minus is applied after the (separated) literal is parsed + BOOST_TEST(boost::int128::int128_t{-1000000} == -1'000'000_i128); + + // Separators are also honored through the convenience macro + BOOST_TEST(boost::int128::int128_t{9999} == BOOST_INT128_INT128_C(9'999)); + + // Full-width value with a separator between every group of three digits + const boost::int128::int128_t max_val {std::numeric_limits::max()}; + BOOST_TEST(max_val == 170'141'183'460'469'231'731'687'303'715'884'105'727_i128); + + // Separators must be usable in a constant expression + static_assert(100'000_i128 == boost::int128::int128_t{100000}, "constexpr separator"); +} + int main() { test_u128_literals(); + test_u128_digit_separators(); test_i128_literals(); + test_i128_digit_separators(); return boost::report_errors(); } From 1c18786ec422416cc49352c8d290b8d164079b1b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 22 Jul 2026 09:48:13 -0400 Subject: [PATCH 2/6] Add if constexpr path that allows stripping of digit separators --- .../boost/int128/detail/mini_from_chars.hpp | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/include/boost/int128/detail/mini_from_chars.hpp b/include/boost/int128/detail/mini_from_chars.hpp index f1c2af6a..aae67169 100644 --- a/include/boost/int128/detail/mini_from_chars.hpp +++ b/include/boost/int128/detail/mini_from_chars.hpp @@ -28,7 +28,7 @@ namespace impl { BOOST_INT128_INLINE_CONSTEXPR unsigned char uchar_values[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, @@ -47,7 +47,7 @@ static_assert(sizeof(uchar_values) == 256, "uchar_values should represent all 25 #endif // __NVCC__ -// Convert characters for 0-9, A-Z, a-z to 0-35. Anything else is 255 +// Convert characters for 0-9, A-Z, a-z to 0-35. The digit separator ' is 254. Anything else is 255 BOOST_INT128_HOST_DEVICE BOOST_INT128_FORCE_INLINE constexpr auto digit_from_char(char val) noexcept -> unsigned char { #if defined(BOOST_INT128_HAS_GPU_SUPPORT) @@ -55,7 +55,7 @@ BOOST_INT128_HOST_DEVICE BOOST_INT128_FORCE_INLINE constexpr auto digit_from_cha constexpr unsigned char uchar_values[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, @@ -77,7 +77,7 @@ BOOST_INT128_HOST_DEVICE BOOST_INT128_FORCE_INLINE constexpr auto digit_from_cha return uchar_values[static_cast(val)]; } -template +template BOOST_INT128_HOST_DEVICE constexpr int from_chars_integer_impl(const char* first, const char* last, Integer& value, int base) noexcept { if (first >= last) @@ -152,7 +152,19 @@ BOOST_INT128_HOST_DEVICE constexpr int from_chars_integer_impl(const char* first for (; i < fast_limit; ++i) { - const auto current_digit = static_cast(digit_from_char(*next)); + const auto raw_digit = digit_from_char(*next); + + // When parsing a user-defined literal skip the digit separator ' (marked as 254) + BOOST_INT128_IF_CONSTEXPR (is_literal_parse) + { + if (raw_digit == 254) + { + ++next; + continue; + } + } + + const auto current_digit = static_cast(raw_digit); if (current_digit >= unsigned_base) { @@ -165,7 +177,19 @@ BOOST_INT128_HOST_DEVICE constexpr int from_chars_integer_impl(const char* first for (; i < nc; ++i) { - const auto current_digit = static_cast(digit_from_char(*next)); + const auto raw_digit = digit_from_char(*next); + + // When parsing a user-defined literal skip the digit separator ' (marked as 254) + BOOST_INT128_IF_CONSTEXPR (is_literal_parse) + { + if (raw_digit == 254) + { + ++next; + continue; + } + } + + const auto current_digit = static_cast(raw_digit); if (current_digit >= unsigned_base) { @@ -229,6 +253,18 @@ BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr int from_chars(const return impl::from_chars_integer_impl(first, last, value, base); } +// Parsing entry points for the user-defined literals. Unlike from_chars these skip the +// C++ digit separator ' so that literals such as 1'234'567_u128 are accepted. +BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr int from_chars_literal(const char* first, const char* last, uint128_t& value, int base = 10) noexcept +{ + return impl::from_chars_integer_impl(first, last, value, base); +} + +BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr int from_chars_literal(const char* first, const char* last, int128_t& value, int base = 10) noexcept +{ + return impl::from_chars_integer_impl(first, last, value, base); +} + } // namespace detail } // namespace int128 } // namespace boost From ccb64bc35f993dcafc16d9ef9770e73af0151332 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 22 Jul 2026 09:48:42 -0400 Subject: [PATCH 3/6] Update driver functions --- include/boost/int128/literals.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/boost/int128/literals.hpp b/include/boost/int128/literals.hpp index 3d388f26..e170f7db 100644 --- a/include/boost/int128/literals.hpp +++ b/include/boost/int128/literals.hpp @@ -18,56 +18,56 @@ namespace literals { BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_u128(const char* str) noexcept { uint128_t result {}; - detail::from_chars(str, str + detail::strlen(str), result); + detail::from_chars_literal(str, str + detail::strlen(str), result); return result; } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_U128(const char* str) noexcept { uint128_t result {}; - detail::from_chars(str, str + detail::strlen(str), result); + detail::from_chars_literal(str, str + detail::strlen(str), result); return result; } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_u128(const char* str, std::size_t len) noexcept { uint128_t result {}; - detail::from_chars(str, str + len, result); + detail::from_chars_literal(str, str + len, result); return result; } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_U128(const char* str, std::size_t len) noexcept { uint128_t result {}; - detail::from_chars(str, str + len, result); + detail::from_chars_literal(str, str + len, result); return result; } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_i128(const char* str) noexcept { int128_t result {}; - detail::from_chars(str, str + detail::strlen(str), result); + detail::from_chars_literal(str, str + detail::strlen(str), result); return result; } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str) noexcept { int128_t result {}; - detail::from_chars(str, str + detail::strlen(str), result); + detail::from_chars_literal(str, str + detail::strlen(str), result); return result; } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_i128(const char* str, std::size_t len) noexcept { int128_t result {}; - detail::from_chars(str, str + len, result); + detail::from_chars_literal(str, str + len, result); return result; } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str, std::size_t len) noexcept { int128_t result {}; - detail::from_chars(str, str + len, result); + detail::from_chars_literal(str, str + len, result); return result; } From b43f924ad396548e8214b682cadbae660adfd86c Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 22 Jul 2026 09:55:44 -0400 Subject: [PATCH 4/6] Cover the string path too --- test/test_literals.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_literals.cpp b/test/test_literals.cpp index f8d16c14..f594b9b8 100644 --- a/test/test_literals.cpp +++ b/test/test_literals.cpp @@ -42,6 +42,10 @@ void test_u128_digit_separators() // Separators are also honored through the convenience macro BOOST_TEST(boost::int128::uint128_t{1234} == BOOST_INT128_UINT128_C(1'234)); + // The string form of the literal skips separators as well + BOOST_TEST(boost::int128::uint128_t{1234} == "1'234"_u128); + BOOST_TEST(boost::int128::uint128_t{1234} == "1'234"_U128); + // Full-width value with a separator between every group of three digits const boost::int128::uint128_t max_val {std::numeric_limits::max()}; BOOST_TEST(max_val == 340'282'366'920'938'463'463'374'607'431'768'211'455_u128); @@ -79,6 +83,10 @@ void test_i128_digit_separators() // Separators are also honored through the convenience macro BOOST_TEST(boost::int128::int128_t{9999} == BOOST_INT128_INT128_C(9'999)); + // The string form of the literal skips separators as well + BOOST_TEST(boost::int128::int128_t{1234} == "1'234"_i128); + BOOST_TEST(boost::int128::int128_t{1234} == "1'234"_I128); + // Full-width value with a separator between every group of three digits const boost::int128::int128_t max_val {std::numeric_limits::max()}; BOOST_TEST(max_val == 170'141'183'460'469'231'731'687'303'715'884'105'727_i128); From a83c93f9b8c91bbdaea27ce45360f00fa7293b19 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 22 Jul 2026 12:17:02 -0400 Subject: [PATCH 5/6] Ignore MSVC 14.1 warning --- test/test_literals.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_literals.cpp b/test/test_literals.cpp index f594b9b8..fe55ad83 100644 --- a/test/test_literals.cpp +++ b/test/test_literals.cpp @@ -32,6 +32,12 @@ void test_u128_literals() BOOST_TEST(max_val == macro_val); } +// Warning with only MSVC 14.1 +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4307) +#endif + void test_u128_digit_separators() { // C++ digit separators (') are ignored inside numeric literals @@ -95,6 +101,10 @@ void test_i128_digit_separators() static_assert(100'000_i128 == boost::int128::int128_t{100000}, "constexpr separator"); } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + int main() { test_u128_literals(); From 700aa6d4851a5269a5d473ceb729ec99a04b4bce Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 22 Jul 2026 16:14:04 -0400 Subject: [PATCH 6/6] Fix constant folding on old compilers --- include/boost/int128/detail/mini_from_chars.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/int128/detail/mini_from_chars.hpp b/include/boost/int128/detail/mini_from_chars.hpp index aae67169..0db8ac56 100644 --- a/include/boost/int128/detail/mini_from_chars.hpp +++ b/include/boost/int128/detail/mini_from_chars.hpp @@ -80,7 +80,7 @@ BOOST_INT128_HOST_DEVICE BOOST_INT128_FORCE_INLINE constexpr auto digit_from_cha template BOOST_INT128_HOST_DEVICE constexpr int from_chars_integer_impl(const char* first, const char* last, Integer& value, int base) noexcept { - if (first >= last) + if (last - first <= 0) { return EINVAL; }