diff --git a/include/boost/int128/detail/mini_from_chars.hpp b/include/boost/int128/detail/mini_from_chars.hpp index f1c2af6a..0db8ac56 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,10 +77,10 @@ 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) + if (last - first <= 0) { return EINVAL; } @@ -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 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; } diff --git a/test/test_literals.cpp b/test/test_literals.cpp index 2e61254d..fe55ad83 100644 --- a/test/test_literals.cpp +++ b/test/test_literals.cpp @@ -32,6 +32,34 @@ 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 + 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)); + + // 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); + + // 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 +77,40 @@ 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)); + + // 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); + + // Separators must be usable in a constant expression + static_assert(100'000_i128 == boost::int128::int128_t{100000}, "constexpr separator"); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + int main() { test_u128_literals(); + test_u128_digit_separators(); test_i128_literals(); + test_i128_digit_separators(); return boost::report_errors(); }