From 81bc28ce59c8723757a7238d13f027c8da347c79 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 14:57:10 -0400 Subject: [PATCH 01/26] First draft of format parser --- include/boost/int128.hpp | 1 + include/boost/int128/format.hpp | 121 ++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 include/boost/int128/format.hpp diff --git a/include/boost/int128.hpp b/include/boost/int128.hpp index 31089d4e..8d91bfce 100644 --- a/include/boost/int128.hpp +++ b/include/boost/int128.hpp @@ -10,5 +10,6 @@ #include #include #include +#include #endif // BOOST_INT128_HPP diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp new file mode 100644 index 00000000..0f38408a --- /dev/null +++ b/include/boost/int128/format.hpp @@ -0,0 +1,121 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_INT128_FORMAT_HPP +#define BOOST_INT128_FORMAT_HPP + +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && \ +((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 18) || (defined(_MSC_VER) && _MSC_VER >= 1940)) + +namespace boost::int128::detail { + +enum class sign_option +{ + plus, + negative, + space, + unset +}; + +template +constexpr auto parse_impl(ParseContext& ctx) +{ + auto it {ctx.begin()}; + int base = 10; + bool is_upper = false; + int padding_digits = 0; + auto sign = sign_option::negative; + bool prefix = false; + bool write_as_character = false; + bool character_debug_format = false; + + // Check for a sign or space + switch (it != ctx.end() ? '1' : *it++) + { + case ' ': + sign = sign_option::space; + break; + case '+': + sign = sign_option::plus; + break; + case '-': + sign = sign_option::negative; + break; + default: + break; + } + + // Alternate form option + if (it != ctx.end() && *it == '#') + { + prefix = true; + ++it; + } + + // Character presentation type + if (it != ctx.end() && (*it == '?' || *it == 'c')) + { + character_debug_format = *it == '?'; + ++it; + } + + // Check for a padding character + while (it != ctx.end() && *it >= '0' && *it <= '9') + { + padding_digits = padding_digits * 10 + (*it - '0'); + ++it; + } + + // Integer presentation + if (it != ctx.end()) + { + switch (*it++) + { + case 'b': + base = 2; + break; + case 'B': + base = 2; + is_upper = true; + break; + + case 'c': + write_as_character = true; + break; + + case 'd': + base = 10; + break; + + case 'x': + base = 16; + break; + case 'X': + base = 16; + is_upper = true; + break; + + default: + break; + } + } + + // Verify we're at the closing brace + if (it != ctx.end() && *it != '}') + { + BOOST_INT128_THROW_EXCEPTION(std::format_error("Expected '}' in format string")); // LCOV_EXCL_LINE + } + + return std::make_tuple(base, is_upper, padding_digits, sign, prefix, write_as_character, character_debug_format); +} + +} // namespace boost::int128::detail + +namespace std { + +} // namespace std + +#endif + +#endif // BOOST_INT128_FORMAT_HPP From b2f34cb91808a4ce5a3149da3758cdc95225e962 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 15:07:30 -0400 Subject: [PATCH 02/26] Fix advancement on sign and throw on bad specifier --- include/boost/int128/format.hpp | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 0f38408a..75aa56f1 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -30,20 +30,25 @@ constexpr auto parse_impl(ParseContext& ctx) bool write_as_character = false; bool character_debug_format = false; - // Check for a sign or space - switch (it != ctx.end() ? '1' : *it++) + // Handle sign or space + if (it != ctx.end()) { - case ' ': - sign = sign_option::space; - break; - case '+': - sign = sign_option::plus; - break; - case '-': - sign = sign_option::negative; - break; - default: - break; + switch (*it) { + case ' ': + sign = sign_option::space; + ++it; + break; + case '+': + sign = sign_option::plus; + ++it; + break; + case '-': + sign = sign_option::negative; + ++it; + break; + default: + break; + } } // Alternate form option @@ -97,7 +102,7 @@ constexpr auto parse_impl(ParseContext& ctx) break; default: - break; + BOOST_INT128_THROW_EXCEPTION(std::format_error("Unsupported format specifier")); } } From 3c981e0530480462dfa66469ee93895acc278bc3 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 15:44:38 -0400 Subject: [PATCH 03/26] Add formatter and parse impl --- include/boost/int128/format.hpp | 54 +++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 75aa56f1..b3185c5e 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -8,14 +8,16 @@ #if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && \ ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 18) || (defined(_MSC_VER) && _MSC_VER >= 1940)) +#include +#include + namespace boost::int128::detail { enum class sign_option { plus, negative, - space, - unset + space }; template @@ -112,13 +114,59 @@ constexpr auto parse_impl(ParseContext& ctx) BOOST_INT128_THROW_EXCEPTION(std::format_error("Expected '}' in format string")); // LCOV_EXCL_LINE } - return std::make_tuple(base, is_upper, padding_digits, sign, prefix, write_as_character, character_debug_format); + return std::make_tuple(base, padding_digits, sign, is_upper, prefix, write_as_character, character_debug_format, it); } +template +struct is_library_type +{ + static constexpr bool value {std::is_same_v || std::is_same_v}; +}; + +template +static constexpr bool is_library_type_v = is_library_type::value; + } // namespace boost::int128::detail namespace std { +template + requires boost::int128::detail::is_library_type::value +struct formatter +{ + int base; + int padding_digits; + boost::int128::detail::sign_option sign; + bool is_upper; + bool prefix; + bool write_as_character; + bool character_debug_format; + + constexpr formatter() : base {10}, + padding_digits {0}, + sign {boost::int128::detail::sign_option::negative}, + is_upper {false}, + prefix {false}, + write_as_character {false}, + character_debug_format {false} + {} + + constexpr auto parse(format_parse_context& ctx) + { + const auto res {boost::int128::detail::parse_impl(ctx)}; + + base = std::get<0>(res); + padding_digits = std::get<1>(res); + sign = std::get<2>(res); + is_upper = std::get<3>(res); + prefix = std::get<4>(res); + write_as_character = std::get<5>(res); + character_debug_format = std::get<6>(res); + + return std::get<7>(res); + } +}; + } // namespace std #endif From 579fef526621d5e9990176fbcc19663a202d602f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 15:46:52 -0400 Subject: [PATCH 04/26] Use concept instead of requires statement --- include/boost/int128/format.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index b3185c5e..3beac729 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -118,20 +118,22 @@ constexpr auto parse_impl(ParseContext& ctx) } template -struct is_library_type +struct is_library_type_impl { static constexpr bool value {std::is_same_v || std::is_same_v}; }; template -static constexpr bool is_library_type_v = is_library_type::value; +static constexpr bool is_library_type_v = is_library_type_impl::value; + +template +concept is_library_type = is_library_type_v; } // namespace boost::int128::detail namespace std { -template - requires boost::int128::detail::is_library_type::value +template struct formatter { int base; From 61beb94ad1143db80ac3c1ba4621b2a356578557 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 15:55:42 -0400 Subject: [PATCH 05/26] Add binary support to mini_to_chars --- include/boost/int128/detail/mini_to_chars.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/boost/int128/detail/mini_to_chars.hpp b/include/boost/int128/detail/mini_to_chars.hpp index f17921c1..eb2ba979 100644 --- a/include/boost/int128/detail/mini_to_chars.hpp +++ b/include/boost/int128/detail/mini_to_chars.hpp @@ -42,6 +42,13 @@ constexpr char* mini_to_chars(char (&buffer)[64], uint128_t v, const int base, c switch (base) { + case 2: + while (v != 0) + { + *last-- = v.low & 1U ? '1' : '0'; + v >>= 1U; + } + case 8: while (v != 0U) { From aedf99e2622afc1e91b5bd4669ebff7f3f22fc24 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 17:05:54 -0400 Subject: [PATCH 06/26] Add formatting function --- include/boost/int128/format.hpp | 89 +++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 3beac729..97db5206 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include namespace boost::int128::detail { @@ -167,6 +169,93 @@ struct formatter return std::get<7>(res); } + + template + auto format(T v, FormatContext& ctx) const + { + auto out {ctx.out()}; + char buffer[64]; + bool isneg {false}; + + if constexpr (std::is_same_v) + { + isneg = v < 0; + v = -v; + } + + const auto end = boost::int128::detail::mini_to_chars(buffer, v, base, is_upper); + std::string_view sv(buffer, end); + std::string s(sv); + + if (prefix) + { + switch (base) + { + case 2: + if (is_upper) + { + s.insert(s.begin(), 'B'); + } + else + { + s.insert(s.begin(), 'b'); + } + s.insert(s.begin(), '0'); + break; + case 8: + s.insert(s.begin(), '0'); + break; + case 16: + if (is_upper) + { + s.insert(s.begin(), 'X'); + } + else + { + s.insert(s.begin(), 'x'); + } + s.insert(s.begin(), '0'); + break; + default: + // Nothing to do + break; + } + } + + // Insert a sign on a positive value if needed + if constexpr (std::is_same_v) + { + if (v > 0) + { + switch (sign) + { + case boost::int128::detail::sign_option::plus: + s.insert(s.begin(), '+'); + break; + case boost::int128::detail::sign_option::space: + s.insert(s.begin(), ' '); + break; + case boost::int128::detail::sign_option::negative: + if (isneg) + { + s.insert(s.begin(), '-'); + } + break; + // LCOV_EXCL_START + default: + BOOST_INT128_UNREACHABLE; + // LCOV_EXCL_STOP + } + } + } + + if (s.size() < static_cast(padding_digits)) + { + s.insert(s.begin(), static_cast(padding_digits) - s.size(), ' '); + } + + return std::copy(s.begin(), s.end(), out); + } }; } // namespace std From 65bdb38b35bf591a1f16ad01ee14ff362b71026a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 17:42:13 -0400 Subject: [PATCH 07/26] Fix sign conversion error --- include/boost/int128/detail/mini_to_chars.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/int128/detail/mini_to_chars.hpp b/include/boost/int128/detail/mini_to_chars.hpp index eb2ba979..4f2c34f8 100644 --- a/include/boost/int128/detail/mini_to_chars.hpp +++ b/include/boost/int128/detail/mini_to_chars.hpp @@ -43,7 +43,7 @@ constexpr char* mini_to_chars(char (&buffer)[64], uint128_t v, const int base, c switch (base) { case 2: - while (v != 0) + while (v != 0U) { *last-- = v.low & 1U ? '1' : '0'; v >>= 1U; From 3dbc2476287a610c7ab87a83e83784700a56e26f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 17:42:22 -0400 Subject: [PATCH 08/26] Add test set shell --- include/boost/int128/format.hpp | 3 +++ test/Jamfile | 2 ++ test/test_format.cpp | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 test/test_format.cpp diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 97db5206..37dd581a 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -12,6 +12,9 @@ #include #include #include +#include + +#define BOOST_INT128_HAS_FORMAT namespace boost::int128::detail { diff --git a/test/Jamfile b/test/Jamfile index d7b91efe..d48d7358 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -75,6 +75,8 @@ run test_x64_msvc_div.cpp ; run test_gcd_lcm.cpp ; +run test_format.cpp ; + # Make sure we run the examples as well run ../examples/construction.cpp ; run ../examples/bit.cpp ; diff --git a/test/test_format.cpp b/test/test_format.cpp new file mode 100644 index 00000000..4ae8d258 --- /dev/null +++ b/test/test_format.cpp @@ -0,0 +1,23 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +#ifdef BOOST_INT128_HAS_FORMAT + +int main() +{ + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif From 4b39bdd85d8c299234ff6a90b0ad4566cc13e230 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 17:50:56 -0400 Subject: [PATCH 09/26] Check for closing brace --- include/boost/int128/format.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 37dd581a..271bd59c 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -80,7 +80,7 @@ constexpr auto parse_impl(ParseContext& ctx) } // Integer presentation - if (it != ctx.end()) + if (it != ctx.end() && *it != '}') { switch (*it++) { From 6132ad996351a6481e2484aedb2f6d59ff4db965 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 17:55:17 -0400 Subject: [PATCH 10/26] Fix string buffer and remove SV step --- include/boost/int128/format.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 271bd59c..5c4352d8 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #define BOOST_INT128_HAS_FORMAT @@ -187,8 +186,7 @@ struct formatter } const auto end = boost::int128::detail::mini_to_chars(buffer, v, base, is_upper); - std::string_view sv(buffer, end); - std::string s(sv); + std::string s(end, buffer + sizeof(buffer)); if (prefix) { From 11a0fcd48154a845d16983e9a749659fe7f603c0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 17:55:26 -0400 Subject: [PATCH 11/26] Fix negative handling --- include/boost/int128/format.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 5c4352d8..5b9338c4 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -181,8 +181,11 @@ struct formatter if constexpr (std::is_same_v) { - isneg = v < 0; - v = -v; + if (v < 0) + { + isneg = true; + v = -v; + } } const auto end = boost::int128::detail::mini_to_chars(buffer, v, base, is_upper); From 17fca7e5e01bf93c9a680b9e14ab19b95e4659cb Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 17:57:02 -0400 Subject: [PATCH 12/26] Add positive and negative empty format string --- test/test_format.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index 4ae8d258..a3c0ed9c 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -8,8 +8,32 @@ #ifdef BOOST_INT128_HAS_FORMAT +template +void test_empty() +{ + BOOST_TEST_CSTR_EQ(std::format("{}", T{2}).c_str(), "2"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{22}).c_str(), "22"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{222}).c_str(), "222"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{2222}).c_str(), "2222"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{22222}).c_str(), "22222"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{222222}).c_str(), "222222"); + + if constexpr (std::is_same_v) + { + BOOST_TEST_CSTR_EQ(std::format("{}", T{-2}).c_str(), "-2"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{-22}).c_str(), "-22"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{-222}).c_str(), "-222"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{-2222}).c_str(), "-2222"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{-22222}).c_str(), "-22222"); + BOOST_TEST_CSTR_EQ(std::format("{}", T{-222222}).c_str(), "-222222"); + } +} + int main() { + test_empty(); + test_empty(); + return boost::report_errors(); } From 3be58de3d25d0b48b02b882bd86df122f9852643 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:00:14 -0400 Subject: [PATCH 13/26] Fix sign insertion --- include/boost/int128/format.hpp | 41 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 5b9338c4..08680b66 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -226,30 +226,33 @@ struct formatter } } - // Insert a sign on a positive value if needed + // Insert our sign if constexpr (std::is_same_v) { - if (v > 0) + switch (sign) { - switch (sign) - { - case boost::int128::detail::sign_option::plus: + case boost::int128::detail::sign_option::plus: + if (!isneg) + { s.insert(s.begin(), '+'); - break; - case boost::int128::detail::sign_option::space: + } + break; + case boost::int128::detail::sign_option::space: + if (!isneg) + { s.insert(s.begin(), ' '); - break; - case boost::int128::detail::sign_option::negative: - if (isneg) - { - s.insert(s.begin(), '-'); - } - break; - // LCOV_EXCL_START - default: - BOOST_INT128_UNREACHABLE; - // LCOV_EXCL_STOP - } + } + break; + case boost::int128::detail::sign_option::negative: + if (isneg) + { + s.insert(s.begin(), '-'); + } + break; + // LCOV_EXCL_START + default: + BOOST_INT128_UNREACHABLE; + // LCOV_EXCL_STOP } } From 252bfea68cd6692827c3c9f597c881f0a8d0586f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:02:40 -0400 Subject: [PATCH 14/26] Fix insertion --- include/boost/int128/detail/mini_to_chars.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/int128/detail/mini_to_chars.hpp b/include/boost/int128/detail/mini_to_chars.hpp index 4f2c34f8..68461f41 100644 --- a/include/boost/int128/detail/mini_to_chars.hpp +++ b/include/boost/int128/detail/mini_to_chars.hpp @@ -45,7 +45,7 @@ constexpr char* mini_to_chars(char (&buffer)[64], uint128_t v, const int base, c case 2: while (v != 0U) { - *last-- = v.low & 1U ? '1' : '0'; + *--last = v.low & 1U ? '1' : '0'; v >>= 1U; } From 3e3fdb90b6b1e6cd86c6f36c32ba094192282c5a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:07:43 -0400 Subject: [PATCH 15/26] Fix padding size --- include/boost/int128/format.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 08680b66..3e643996 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -256,9 +256,9 @@ struct formatter } } - if (s.size() < static_cast(padding_digits)) + if (s.size() - 1u < static_cast(padding_digits)) { - s.insert(s.begin(), static_cast(padding_digits) - s.size(), ' '); + s.insert(s.begin(), static_cast(padding_digits) - s.size() + 1u, ' '); } return std::copy(s.begin(), s.end(), out); From f0de9225b3ac5b5063b7ccec255d00cbaea0d3af Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:07:50 -0400 Subject: [PATCH 16/26] Add various binary tests --- test/test_format.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index a3c0ed9c..2afb422d 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -29,11 +29,30 @@ void test_empty() } } +template +void test_binary() +{ + BOOST_TEST_CSTR_EQ(std::format("{:b}", T{2}).c_str(), "10"); + BOOST_TEST_CSTR_EQ(std::format("{:#b}", T{2}).c_str(), "0b10"); + BOOST_TEST_CSTR_EQ(std::format("{:#B}", T{2}).c_str(), "0B10"); + + BOOST_TEST_CSTR_EQ(std::format("{:b}", T{5}).c_str(), "101"); + BOOST_TEST_CSTR_EQ(std::format("{:#b}", T{5}).c_str(), "0b101"); + BOOST_TEST_CSTR_EQ(std::format("{:#B}", T{5}).c_str(), "0B101"); + + BOOST_TEST_CSTR_EQ(std::format("{:6b}", T{5}).c_str(), " 101"); + BOOST_TEST_CSTR_EQ(std::format("{:#6b}", T{5}).c_str(), " 0b101"); + BOOST_TEST_CSTR_EQ(std::format("{:#06B}", T{5}).c_str(), " 0B101"); +} + int main() { test_empty(); test_empty(); + test_binary(); + test_binary(); + return boost::report_errors(); } From 97c48463f84a1ebd4d5edaae1e4a2eab8aa295f7 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:09:47 -0400 Subject: [PATCH 17/26] Fix zeros location per spec --- include/boost/int128/format.hpp | 10 +++++----- test/test_format.cpp | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 3e643996..a64876a6 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -191,6 +191,11 @@ struct formatter const auto end = boost::int128::detail::mini_to_chars(buffer, v, base, is_upper); std::string s(end, buffer + sizeof(buffer)); + if (s.size() - 1u < static_cast(padding_digits)) + { + s.insert(s.begin(), static_cast(padding_digits) - s.size() + 1u, '0'); + } + if (prefix) { switch (base) @@ -256,11 +261,6 @@ struct formatter } } - if (s.size() - 1u < static_cast(padding_digits)) - { - s.insert(s.begin(), static_cast(padding_digits) - s.size() + 1u, ' '); - } - return std::copy(s.begin(), s.end(), out); } }; diff --git a/test/test_format.cpp b/test/test_format.cpp index 2afb422d..bd42c713 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -40,9 +40,9 @@ void test_binary() BOOST_TEST_CSTR_EQ(std::format("{:#b}", T{5}).c_str(), "0b101"); BOOST_TEST_CSTR_EQ(std::format("{:#B}", T{5}).c_str(), "0B101"); - BOOST_TEST_CSTR_EQ(std::format("{:6b}", T{5}).c_str(), " 101"); - BOOST_TEST_CSTR_EQ(std::format("{:#6b}", T{5}).c_str(), " 0b101"); - BOOST_TEST_CSTR_EQ(std::format("{:#06B}", T{5}).c_str(), " 0B101"); + BOOST_TEST_CSTR_EQ(std::format("{:6b}", T{5}).c_str(), "000101"); + BOOST_TEST_CSTR_EQ(std::format("{:#6b}", T{5}).c_str(), "0b000101"); + BOOST_TEST_CSTR_EQ(std::format("{:#06B}", T{5}).c_str(), "0B000101"); } int main() From 608bcac0ad094341c8273b432b3c5905507f65ab Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:14:18 -0400 Subject: [PATCH 18/26] Fix octal formatting --- include/boost/int128/format.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index a64876a6..94e16aee 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -95,6 +95,10 @@ constexpr auto parse_impl(ParseContext& ctx) write_as_character = true; break; + case 'o': + base = 8; + break; + case 'd': base = 10; break; @@ -211,9 +215,6 @@ struct formatter } s.insert(s.begin(), '0'); break; - case 8: - s.insert(s.begin(), '0'); - break; case 16: if (is_upper) { From 6a76cbf14223ef7c09e94a6e37a3dd187a5f5267 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:14:28 -0400 Subject: [PATCH 19/26] Add octal testing --- test/test_format.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index bd42c713..319bb83d 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -45,6 +45,16 @@ void test_binary() BOOST_TEST_CSTR_EQ(std::format("{:#06B}", T{5}).c_str(), "0B000101"); } +template +void test_octal() +{ + BOOST_TEST_CSTR_EQ(std::format("{:o}", T{42}).c_str(), "52"); + BOOST_TEST_CSTR_EQ(std::format("{:#o}", T{42}).c_str(), "52"); + + BOOST_TEST_CSTR_EQ(std::format("{:4o}", T{42}).c_str(), "0052"); + BOOST_TEST_CSTR_EQ(std::format("{:#4o}", T{42}).c_str(), "0052"); +} + int main() { test_empty(); @@ -53,6 +63,9 @@ int main() test_binary(); test_binary(); + test_octal(); + test_octal(); + return boost::report_errors(); } From 8f60555d081e74a7aca2ca7d9600205ef1c432a7 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:27:42 -0400 Subject: [PATCH 20/26] Octal reacts to # --- include/boost/int128/format.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 94e16aee..46bed8fc 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -215,6 +215,9 @@ struct formatter } s.insert(s.begin(), '0'); break; + case 8: + s.insert(s.begin(), '0'); + break; case 16: if (is_upper) { From 7f959c77766d64bc05378058eb75a94d05fc78d0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:27:54 -0400 Subject: [PATCH 21/26] Fix sign insertion for unsigned types --- include/boost/int128/format.hpp | 49 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 46bed8fc..f777a2bc 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -236,33 +236,30 @@ struct formatter } // Insert our sign - if constexpr (std::is_same_v) + switch (sign) { - switch (sign) - { - case boost::int128::detail::sign_option::plus: - if (!isneg) - { - s.insert(s.begin(), '+'); - } - break; - case boost::int128::detail::sign_option::space: - if (!isneg) - { - s.insert(s.begin(), ' '); - } - break; - case boost::int128::detail::sign_option::negative: - if (isneg) - { - s.insert(s.begin(), '-'); - } - break; - // LCOV_EXCL_START - default: - BOOST_INT128_UNREACHABLE; - // LCOV_EXCL_STOP - } + case boost::int128::detail::sign_option::plus: + if (!isneg) + { + s.insert(s.begin(), '+'); + } + break; + case boost::int128::detail::sign_option::space: + if (!isneg) + { + s.insert(s.begin(), ' '); + } + break; + case boost::int128::detail::sign_option::negative: + if (isneg) + { + s.insert(s.begin(), '-'); + } + break; + // LCOV_EXCL_START + default: + BOOST_INT128_UNREACHABLE; + // LCOV_EXCL_STOP } return std::copy(s.begin(), s.end(), out); From 56fb6188c71506869fa810aefe786f5b6f7a0289 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:29:16 -0400 Subject: [PATCH 22/26] Add decimal value testing --- test/test_format.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/test/test_format.cpp b/test/test_format.cpp index 319bb83d..5656bed2 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -49,10 +49,26 @@ template void test_octal() { BOOST_TEST_CSTR_EQ(std::format("{:o}", T{42}).c_str(), "52"); - BOOST_TEST_CSTR_EQ(std::format("{:#o}", T{42}).c_str(), "52"); + BOOST_TEST_CSTR_EQ(std::format("{:#o}", T{42}).c_str(), "052"); BOOST_TEST_CSTR_EQ(std::format("{:4o}", T{42}).c_str(), "0052"); - BOOST_TEST_CSTR_EQ(std::format("{:#4o}", T{42}).c_str(), "0052"); + BOOST_TEST_CSTR_EQ(std::format("{:#4o}", T{42}).c_str(), "00052"); +} + +template +void test_decimal() +{ + BOOST_TEST_CSTR_EQ(std::format("{:d}", T{42}).c_str(), "42"); + BOOST_TEST_CSTR_EQ(std::format("{:#d}", T{42}).c_str(), "42"); + + BOOST_TEST_CSTR_EQ(std::format("{:+d}", T{42}).c_str(), "+42"); + BOOST_TEST_CSTR_EQ(std::format("{:+#d}", T{42}).c_str(), "+42"); + + BOOST_TEST_CSTR_EQ(std::format("{: d}", T{42}).c_str(), " 42"); + BOOST_TEST_CSTR_EQ(std::format("{: #d}", T{42}).c_str(), " 42"); + + BOOST_TEST_CSTR_EQ(std::format("{:+3d}", T{42}).c_str(), "+042"); + BOOST_TEST_CSTR_EQ(std::format("{:+#3d}", T{42}).c_str(), "+042"); } int main() @@ -66,6 +82,9 @@ int main() test_octal(); test_octal(); + test_decimal(); + test_decimal(); + return boost::report_errors(); } From e6ca5f06d721daf8a9dc0cbe05ab1ad43b9f612d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 18:56:46 -0400 Subject: [PATCH 23/26] Fix sign handling --- include/boost/int128/format.hpp | 14 ++++++++++++-- test/test_format.cpp | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index f777a2bc..7a86ef20 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -249,11 +249,21 @@ struct formatter { s.insert(s.begin(), ' '); } + if constexpr (std::is_same_v) + { + if (isneg) + { + s.insert(s.begin(), '-'); + } + } break; case boost::int128::detail::sign_option::negative: - if (isneg) + if constexpr (std::is_same_v) { - s.insert(s.begin(), '-'); + if (isneg) + { + s.insert(s.begin(), '-'); + } } break; // LCOV_EXCL_START diff --git a/test/test_format.cpp b/test/test_format.cpp index 5656bed2..1a50aebb 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -69,6 +69,21 @@ void test_decimal() BOOST_TEST_CSTR_EQ(std::format("{:+3d}", T{42}).c_str(), "+042"); BOOST_TEST_CSTR_EQ(std::format("{:+#3d}", T{42}).c_str(), "+042"); + + BOOST_TEST_CSTR_EQ(std::format("{:-3d}", T{42}).c_str(), "042"); + BOOST_TEST_CSTR_EQ(std::format("{:-#3d}", T{42}).c_str(), "042"); + + if constexpr (std::is_same_v) + { + BOOST_TEST_CSTR_EQ(std::format("{: 3d}", T{42}).c_str(), " 042"); + BOOST_TEST_CSTR_EQ(std::format("{: #3d}", T{42}).c_str(), " 042"); + + BOOST_TEST_CSTR_EQ(std::format("{:-3d}", T{-42}).c_str(), "-042"); + BOOST_TEST_CSTR_EQ(std::format("{:-#3d}", T{-42}).c_str(), "-042"); + + BOOST_TEST_CSTR_EQ(std::format("{: 3d}", T{-42}).c_str(), "-042"); + BOOST_TEST_CSTR_EQ(std::format("{: #3d}", T{-42}).c_str(), "-042"); + } } int main() From 148f4c676f0ceee82c0f354be8daac663107fcdd Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 8 Aug 2025 20:47:17 -0400 Subject: [PATCH 24/26] Fix fallthrough --- include/boost/int128/detail/mini_to_chars.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/int128/detail/mini_to_chars.hpp b/include/boost/int128/detail/mini_to_chars.hpp index 68461f41..7286dbab 100644 --- a/include/boost/int128/detail/mini_to_chars.hpp +++ b/include/boost/int128/detail/mini_to_chars.hpp @@ -48,6 +48,7 @@ constexpr char* mini_to_chars(char (&buffer)[64], uint128_t v, const int base, c *--last = v.low & 1U ? '1' : '0'; v >>= 1U; } + break; case 8: while (v != 0U) From 47a61b067457ba6a4e8ee5d6c62a19b7d38ed781 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Sun, 10 Aug 2025 16:34:31 -0400 Subject: [PATCH 25/26] Ignore coverage on consteval throwing line --- include/boost/int128/format.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 7a86ef20..e694f260 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -110,9 +110,10 @@ constexpr auto parse_impl(ParseContext& ctx) base = 16; is_upper = true; break; - + // LCOV_EXCL_START default: BOOST_INT128_THROW_EXCEPTION(std::format_error("Unsupported format specifier")); + // LCOV_EXCL_STOP } } From 45509504d39d925a90dfb4e09ed77d67f0b48dcb Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Sun, 10 Aug 2025 19:57:38 -0400 Subject: [PATCH 26/26] Add hex testing --- test/test_format.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index 1a50aebb..9064e44d 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -86,6 +86,34 @@ void test_decimal() } } +template +void test_hex() +{ + BOOST_TEST_CSTR_EQ(std::format("{:x}", T{42}).c_str(), "2a"); + BOOST_TEST_CSTR_EQ(std::format("{:#x}", T{42}).c_str(), "0x2a"); + + BOOST_TEST_CSTR_EQ(std::format("{:X}", T{42}).c_str(), "2A"); + BOOST_TEST_CSTR_EQ(std::format("{:#X}", T{42}).c_str(), "0X2A"); + + BOOST_TEST_CSTR_EQ(std::format("{: X}", T{42}).c_str(), " 2A"); + BOOST_TEST_CSTR_EQ(std::format("{: #X}", T{42}).c_str(), " 0X2A"); + + BOOST_TEST_CSTR_EQ(std::format("{:+X}", T{42}).c_str(), "+2A"); + BOOST_TEST_CSTR_EQ(std::format("{:+#X}", T{42}).c_str(), "+0X2A"); + + if constexpr (std::is_same_v) + { + BOOST_TEST_CSTR_EQ(std::format("{:-X}", T{-42}).c_str(), "-2A"); + BOOST_TEST_CSTR_EQ(std::format("{:-#X}", T{-42}).c_str(), "-0X2A"); + } + + BOOST_TEST_CSTR_EQ(std::format("{:5X}", T{42}).c_str(), "0002A"); + BOOST_TEST_CSTR_EQ(std::format("{:#5X}", T{42}).c_str(), "0X0002A"); + + BOOST_TEST_CSTR_EQ(std::format("{: 5X}", T{42}).c_str(), " 0002A"); + BOOST_TEST_CSTR_EQ(std::format("{: #5X}", T{42}).c_str(), " 0X0002A"); +} + int main() { test_empty(); @@ -100,6 +128,9 @@ int main() test_decimal(); test_decimal(); + test_hex(); + test_hex(); + return boost::report_errors(); }