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/detail/mini_to_chars.hpp b/include/boost/int128/detail/mini_to_chars.hpp index f17921c1..7286dbab 100644 --- a/include/boost/int128/detail/mini_to_chars.hpp +++ b/include/boost/int128/detail/mini_to_chars.hpp @@ -42,6 +42,14 @@ constexpr char* mini_to_chars(char (&buffer)[64], uint128_t v, const int base, c switch (base) { + case 2: + while (v != 0U) + { + *--last = v.low & 1U ? '1' : '0'; + v >>= 1U; + } + break; + case 8: while (v != 0U) { diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp new file mode 100644 index 00000000..e694f260 --- /dev/null +++ b/include/boost/int128/format.hpp @@ -0,0 +1,284 @@ +// 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)) + +#include +#include +#include +#include + +#define BOOST_INT128_HAS_FORMAT + +namespace boost::int128::detail { + +enum class sign_option +{ + plus, + negative, + space +}; + +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; + + // Handle sign or space + if (it != ctx.end()) + { + 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 + 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() && *it != '}') + { + switch (*it++) + { + case 'b': + base = 2; + break; + case 'B': + base = 2; + is_upper = true; + break; + + case 'c': + write_as_character = true; + break; + + case 'o': + base = 8; + break; + + case 'd': + base = 10; + break; + + case 'x': + base = 16; + break; + case 'X': + base = 16; + is_upper = true; + break; + // LCOV_EXCL_START + default: + BOOST_INT128_THROW_EXCEPTION(std::format_error("Unsupported format specifier")); + // LCOV_EXCL_STOP + } + } + + // 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, padding_digits, sign, is_upper, prefix, write_as_character, character_debug_format, it); +} + +template +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_impl::value; + +template +concept is_library_type = is_library_type_v; + +} // namespace boost::int128::detail + +namespace std { + +template +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); + } + + template + auto format(T v, FormatContext& ctx) const + { + auto out {ctx.out()}; + char buffer[64]; + bool isneg {false}; + + if constexpr (std::is_same_v) + { + if (v < 0) + { + isneg = true; + v = -v; + } + } + + 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) + { + 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 our 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(), ' '); + } + if constexpr (std::is_same_v) + { + if (isneg) + { + s.insert(s.begin(), '-'); + } + } + break; + case boost::int128::detail::sign_option::negative: + if constexpr (std::is_same_v) + { + 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); + } +}; + +} // namespace std + +#endif + +#endif // BOOST_INT128_FORMAT_HPP 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..9064e44d --- /dev/null +++ b/test/test_format.cpp @@ -0,0 +1,144 @@ +// 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 + +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"); + } +} + +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(), "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"); +} + +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(), "052"); + + 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"); + + 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"); + } +} + +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(); + test_empty(); + + test_binary(); + test_binary(); + + test_octal(); + test_octal(); + + test_decimal(); + test_decimal(); + + test_hex(); + test_hex(); + + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif