diff --git a/include/boost/multiprecision/detail/format.hpp b/include/boost/multiprecision/detail/format.hpp new file mode 100644 index 000000000..3e8caea4a --- /dev/null +++ b/include/boost/multiprecision/detail/format.hpp @@ -0,0 +1,365 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_MP_DETAIL_FORMAT_HPP +#define BOOST_MP_DETAIL_FORMAT_HPP + +#ifdef BOOST_NO_CXX20_HDR_FORMAT +#error "This header should not be included in C++20 mode" +#endif + +#include +#include +#include + +namespace boost { + namespace multiprecision + { + namespace detail + { + template + std::string print_binary_string(const Number&) + { + throw std::format_error("Binary string not supported for this number type."); + } + template + std::string print_binary_string(const number, ExpressionTemplates>& value) + { + std::string result; + + if(value < 0) + throw std::format_error("Binary string not supported negative values in sign-magnitude format."); + const limb_type* plimbs = value.backend().limbs(); + std::size_t count = value.backend().size(); + + // Format each limb: + for (int i = count - 1; i >= 0; --i) + result += std::format("{:0{}b}", plimbs[i], sizeof(limb_type) * CHAR_BIT); + + // remove leading zeros: + std::string::size_type pos = result.find('1'); + if (pos != std::string::npos) + result.erase(0, pos); + + return result; + } + template + std::string print_binary_string(const number& value); + + template + inline std::basic_string make_wide_string(const std::string& s) + { + return std::basic_string(s.begin(), s.end()); + } + template<> + inline std::basic_string make_wide_string(const std::string& s) + { + return s; + } + + template + void insert_locale_grouping(std::basic_string& s, const std::locale& loc, std::size_t start) + { + const std::numpunct& facet = std::use_facet>(loc); + charT thousands_separator = facet.thousands_sep(); + std::string punct = facet.grouping(); + if (punct.empty()) + return; + std::size_t current_separator = 0; + std::size_t current_position = s.size(); + + while ((punct[current_separator] <= current_position) && (current_position - punct[current_separator] > start)) + { + current_position -= punct[current_separator]; + s.insert(current_position, 1, thousands_separator); + if (current_separator + 1 < punct.size()) + ++current_separator; + } + } + + + template + class number_formatter + { + enum fill_kind { none, left, right, centre }; + public: + constexpr typename std::basic_format_parse_context::iterator parse(const std::basic_format_parse_context& context) + { + auto start = context.begin(); + auto end = context.end(); + + if ((start != end) && (start + 1 != end)) + { + // We may have fill field: + if (*(start + 1) == '<') + { + align = left; + fill = *start; + start += 2; + } + else if (*(start + 1) == '>') + { + align = right; + fill = *start; + start += 2; + } + else if (*(start + 1) == '^') + { + align = centre; + fill = *start; + start += 2; + } + } + if ((start == end) || (*start == '}')) + return start; + + // Start looking for alignments, with no fill specified (we'll use a space): + if (*start == '<') + { + align = left; + ++start; + } + else if (*start == '>') + { + align = right; + ++start; + } + else if (*start == '^') + { + align = centre; + ++start; + } + if ((start == end) || (*start == '}')) + return start; + + // Sign handling: + if (*start == '+') + { + flags |= std::ios_base::showpos; + ++start; + } + else if (*start == '-') + { + ++start; + } + else if (*start == ' ') + { + space_before_positive = true; + ++start; + } + else if (*start == '#') + { + flags |= std::ios_base::showbase; + ++start; + } + if ((start == end) || (*start == '}')) + return start; + + // zero padding: + if (*start == '0') + { + pad_with_zeros = true; + ++start; + } + if ((start == end) || (*start == '}')) + return start; + + // Width: + if ((*start >= '0') && (*start <= '9')) + { + width = *start - '0'; + while ((++start != end) && (*start >= '0') && (*start <= '9')) + { + width *= 10; + width += *start - '0'; + } + } + if ((start == end) || (*start == '}')) + return start; + + // Precision: + if (*start == '.') + { + ++start; + if ((*start >= '0') && (*start <= '9')) + { + precision = *start - '0'; + while ((++start != end) && (*start >= '0') && (*start <= '9')) + { + precision *= 10; + precision += *start - '0'; + } + } + else + throw std::format_error("Precision specifier followed by non-numeric context"); + } + if ((start == end) || (*start == '}')) + return start; + + // Locale: + if (*start == 'L') + { + use_locale = true; + ++start; + } + if ((start == end) || (*start == '}')) + return start; + + // Type format: + if (*start == 'B') + { + binary_out = true; + flags |= std::ios_base::uppercase; + ++start; + } + else if (*start == 'b') + { + binary_out = true; + ++start; + } + else if (*start == 'c') + { + char_out = true; + ++start; + } + else if (*start == 'd') + { + ++start; + } + else if (*start == 'o') + { + flags |= std::ios_base::oct; + ++start; + } + else if (*start == 'x') + { + flags |= std::ios_base::hex; + ++start; + } + else if (*start == 'X') + { + flags |= std::ios_base::hex | std::ios_base::uppercase; + ++start; + } + + // Error handling: + if ((start != end) && (*start != '}')) + { + // We have an error: +#ifndef BOOST_NO_EXCEPTIONS + std::string msg1("Unexpected format specifier \""), msg2("\" encountered while formatting Boost.Multiprecision integer type"); + throw std::format_error(msg1 + static_cast(*start) + msg2); +#else + // All we can do is ignore the erroneous characters! + while ((start != end) && (*start != '}')) + ++start; +#endif + } + return start; + } + + template + OutputIterator format(const Number& value, std::basic_format_context& context)const + { + auto&& out = context.out(); + std::basic_string s; + if (char_out) + { + static const Number max = std::numeric_limits::max(); + static const Number min = std::numeric_limits::is_signed ? std::numeric_limits::min() : 0; + if((value <= max) && (value >= min)) + s = static_cast(value); + else + throw std::format_error("Value was outside the range of type charT"); + } + else if (binary_out) + { + if (flags & std::ios_base::showbase) + { + if (flags & std::ios_base::uppercase) + { + static const charT c[] = { '0', 'B', 0 }; + s = c; + } + else + { + static const charT c[] = { '0', 'b', 0 }; + s = c; + } + } + s += make_wide_string(print_binary_string(value)); + } + else + s = make_wide_string(value.str(precision, flags)); + // Locale specific grouping: + if (use_locale) + { + std::size_t start = 0; + if (flags & std::ios_base::showbase) + { + if (binary_out) + start = 2; + else if (flags & std::ios_base::hex) + start = 2; + else if (flags & std::ios_base::oct) + start = 1; + } + insert_locale_grouping(s, context.locale(), start); + } + if (space_before_positive && (value > 0)) + s.insert(0, 1, static_cast(' ')); + else if((flags & std::ios_base::showpos) && (s[0] != '+') && (s[0] != '-')) + s.insert(0, 1, static_cast('+')); + if ((width > s.size()) && (align == none) && pad_with_zeros) + { + std::size_t pos = 0; + if ((s[0] == ' ') || (s[0] == '+') || (s[0] == '-')) + ++pos; + s.insert(pos, width - s.size(), static_cast('0')); + } + std::size_t n_fill_left{ 0 }, n_fill_right{ 0 }; + if (width > s.size()) + { + if (align == centre) + { + n_fill_left = (width - s.size()) / 2; + n_fill_right = width - s.size() - n_fill_left; + } + else if (align == left) + n_fill_right = width - s.size(); + else if (align == right) + n_fill_left = width - s.size(); + } + if (n_fill_left) + out = std::fill_n(out, n_fill_left, fill); + out = std::copy(s.begin(), s.end(), out); + if (n_fill_right) + out = std::fill_n(out, n_fill_right, fill); + return out; + } + private: + std::ios_base::fmtflags flags = std::ios_base::fmtflags(0); + charT fill = ' '; + fill_kind align = none; + bool space_before_positive = false; + std::size_t width = 0; + std::size_t precision = 6; + bool use_locale = false; + bool binary_out = false; + bool char_out = false; + bool pad_with_zeros = false; + }; + + } + } +} + +namespace std +{ + template + struct formatter, charT> : public boost::multiprecision::detail::number_formatter, charT, boost::multiprecision::number_category>::value> {}; +} + +#endif // BOOST_MP_DETAIL_FORMAT_HPP diff --git a/include/boost/multiprecision/gmp.hpp b/include/boost/multiprecision/gmp.hpp index fdb848fef..9041f9fd6 100644 --- a/include/boost/multiprecision/gmp.hpp +++ b/include/boost/multiprecision/gmp.hpp @@ -3385,6 +3385,27 @@ namespace detail { template <> struct is_variable_precision > : public std::integral_constant {}; + +#ifndef BOOST_NO_CXX20_HDR_FORMAT +template +std::string print_binary_string(const number& value) +{ + if (value < 0) + throw std::format_error("Binary string not supported negative values in sign-magnitude format."); + + boost::multiprecision::backends::detail::gmp_char_ptr ps(mpz_get_str(nullptr, 2, value.backend().data())); + + std::string result(ps.get()); + + // remove leading zeros: + std::string::size_type pos = result.find('1'); + if (pos != std::string::npos) + result.erase(0, pos); + + return result; +} +#endif + } // namespace detail } // namespace multiprecision diff --git a/include/boost/multiprecision/number.hpp b/include/boost/multiprecision/number.hpp index 33772103b..14222dfc4 100644 --- a/include/boost/multiprecision/number.hpp +++ b/include/boost/multiprecision/number.hpp @@ -16,6 +16,9 @@ #include #include #include +#ifndef BOOST_NO_CXX20_HDR_FORMAT +#include +#endif #include // stream operators #include // EOF #include // isspace diff --git a/include/boost/multiprecision/tommath.hpp b/include/boost/multiprecision/tommath.hpp index 2e2530f7e..44e1a85d9 100644 --- a/include/boost/multiprecision/tommath.hpp +++ b/include/boost/multiprecision/tommath.hpp @@ -909,6 +909,33 @@ template <> struct number_category : public std::integral_constant {}; +#ifndef BOOST_NO_CXX20_HDR_FORMAT +template +std::string print_binary_string(const number& value) +{ + if (value < 0) + throw std::format_error("Binary string not supported negative values in sign-magnitude format."); + + int s; + boost::multiprecision::backends::detail::check_tommath_result(mp_radix_size(const_cast<::mp_int*>(&value.backend().data()), 2, &s)); + std::unique_ptr a(new char[s + 1]); +#ifndef mp_to_binary + boost::multiprecision::backends::detail::check_tommath_result(mp_toradix_n(const_cast<::mp_int*>(&value.backend().data()), a.get(), 2, s + 1)); +#else + std::size_t written; + boost::multiprecision::backends::detail::check_tommath_result(mp_to_radix(const_cast<::mp_int*>(&value.backend().data()), a.get(), s + 1, &written, 2)); +#endif + std::string result = a.get(); + + // remove leading zeros: + std::string::size_type pos = result.find('1'); + if (pos != std::string::npos) + result.erase(0, pos); + + return result; +} +#endif + } } // namespace boost::multiprecision diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index b0d8c1e2d..4ce8ba265 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -1651,6 +1651,15 @@ rule get_concept_checks return $(result) ; } +test-suite format : + [ run test_format_cpp_int.cpp : : : [ requires cxx20_hdr_format ] : test_format_cpp_int_narrow ] + [ run test_format_cpp_int.cpp : : : [ requires cxx20_hdr_format ] TEST_WIDE_CHAR : test_format_cpp_int_wide ] + [ run test_format_mpz_int.cpp gmp : : : [ check-target-builds ../config//has_gmp : : no ] [ requires cxx20_hdr_format ] : test_format_mpz_int_narrow ] + [ run test_format_mpz_int.cpp gmp : : : [ check-target-builds ../config//has_gmp : : no ] [ requires cxx20_hdr_format ] TEST_WIDE_CHAR : test_format_mpz_int_wide ] + [ run test_format_tom_int.cpp tommath : : : [ check-target-builds ../config//has_tommath : : no ] [ requires cxx20_hdr_format ] : test_format_tom_int_narrow ] + [ run test_format_tom_int.cpp tommath : : : [ check-target-builds ../config//has_tommath : : no ] [ requires cxx20_hdr_format ] TEST_WIDE_CHAR : test_format_tom_int_wide ] +; + test-suite concepts : [ get_concept_checks ] ; test-suite examples : ../example//examples ; @@ -1658,7 +1667,7 @@ test-suite performance : ../performance//performance ; # Some aliases which group blocks of tests for CI testing: -alias github_ci_block_1 : arithmetic_tests functions_and_limits conversions ; +alias github_ci_block_1 : arithmetic_tests functions_and_limits conversions format ; alias github_ci_block_2 : cpp_int_tests misc compile_fail concepts examples ; explicit github_ci_block_1 ; explicit github_ci_block_2 ; diff --git a/test/test_format.hpp b/test/test_format.hpp new file mode 100644 index 000000000..6cdf0ba37 --- /dev/null +++ b/test/test_format.hpp @@ -0,0 +1,113 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_MP_TEST_FORMAT_HPP +#define BOOST_MP_TEST_FORMAT_HPP + +#include "test.hpp" +#include + +#ifdef BOOST_NO_CXX20_HDR_FORMAT +#error "No support for std::format!!" +#endif + +#ifdef TEST_WIDE_CHAR +#define C(x) BOOST_JOIN(L, x) + +// To keep our testing framework happy: +std::ostream& operator<<(std::ostream& os, const std::wstring& s) +{ + std::string ns(s.begin(), s.end()); + return os << ns; +} + +using string_type = std::wstring; + +#else +#define C(x) x +using string_type = std::string; +#endif + +template +void test_integer() +{ + Number val(3); + Number mval(-3); + + BOOST_CHECK_EQUAL(std::format(C("{}"), val), string_type(C("3"))); + BOOST_CHECK_EQUAL(std::format(C("{:<}"), val), string_type(C("3"))); + BOOST_CHECK_EQUAL(std::format(C("{:^}"), val), string_type(C("3"))); + BOOST_CHECK_EQUAL(std::format(C("{:>}"), val), string_type(C("3"))); + BOOST_CHECK_EQUAL(std::format(C("{:<10}"), val), string_type(C("3 "))); + BOOST_CHECK_EQUAL(std::format(C("{:^8}"), val), string_type(C(" 3 "))); + BOOST_CHECK_EQUAL(std::format(C("{:>20}"), val), string_type(C(" 3"))); + BOOST_CHECK_EQUAL(std::format(C("{: <10}"), val), string_type(C("3 "))); + BOOST_CHECK_EQUAL(std::format(C("{: ^8}"), val), string_type(C(" 3 "))); + BOOST_CHECK_EQUAL(std::format(C("{: >20}"), val), string_type(C(" 3"))); + BOOST_CHECK_EQUAL(std::format(C("{:#<10}"), val), string_type(C("3#########"))); + BOOST_CHECK_EQUAL(std::format(C("{:#^8}"), val), string_type(C("###3####"))); + BOOST_CHECK_EQUAL(std::format(C("{:#>20}"), val), string_type(C("###################3"))); + + BOOST_CHECK_EQUAL(std::format(C("{:+}"), val), string_type(C("+3"))); + BOOST_CHECK_EQUAL(std::format(C("{:-}"), val), string_type(C("3"))); + BOOST_CHECK_EQUAL(std::format(C("{: }"), val), string_type(C(" 3"))); + if constexpr (std::numeric_limits::is_signed) + { + BOOST_CHECK_EQUAL(std::format(C("{:+}"), mval), string_type(C("-3"))); + BOOST_CHECK_EQUAL(std::format(C("{:-}"), mval), string_type(C("-3"))); + BOOST_CHECK_EQUAL(std::format(C("{: }"), mval), string_type(C("-3"))); + } + BOOST_CHECK_EQUAL(std::format(C("{:+05}"), val), string_type(C("+0003"))); + BOOST_CHECK_EQUAL(std::format(C("{:-05}"), val), string_type(C("00003"))); + BOOST_CHECK_EQUAL(std::format(C("{: 05}"), val), string_type(C(" 0003"))); + if constexpr (std::numeric_limits::is_signed) + { + BOOST_CHECK_EQUAL(std::format(C("{:+05}"), mval), string_type(C("-0003"))); + BOOST_CHECK_EQUAL(std::format(C("{:-05}"), mval), string_type(C("-0003"))); + BOOST_CHECK_EQUAL(std::format(C("{: 05}"), mval), string_type(C("-0003"))); + } + + BOOST_CHECK_EQUAL(std::format(C("{:.10}"), val), string_type(C("3"))); // precision is parsed by inetegers, but has no effect. + //BOOST_CHECK_EQUAL(std::format(C("{:L}"), val), string_type(C("3"))); + + val = 324; + BOOST_CHECK_EQUAL(std::format(C("{:o}"), val), string_type(C("504"))); + BOOST_CHECK_EQUAL(std::format(C("{:#o}"), val), string_type(C("0504"))); + BOOST_CHECK_EQUAL(std::format(C("{:x}"), val), string_type(C("144"))); + BOOST_CHECK_EQUAL(std::format(C("{:#x}"), val), string_type(C("0x144"))); + BOOST_CHECK_EQUAL(std::format(C("{:#X}"), val), string_type(C("0X144"))); + BOOST_CHECK_EQUAL(std::format(C("{:d}"), val), string_type(C("324"))); + BOOST_CHECK_EQUAL(std::format(C("{:#d}"), val), string_type(C("324"))); + BOOST_CHECK_EQUAL(std::format(C("{:b}"), val), string_type(C("101000100"))); + BOOST_CHECK_EQUAL(std::format(C("{:#b}"), val), string_type(C("0b101000100"))); + BOOST_CHECK_EQUAL(std::format(C("{:#B}"), val), string_type(C("0B101000100"))); + val <<= 100; + BOOST_CHECK_EQUAL(std::format(C("{:#B}"), val), string_type(C("0B1010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"))); + + try + { + std::locale loc("en"); + std::locale cloc("C"); + BOOST_CHECK_EQUAL(std::format(cloc, C("{:L}"), val), string_type(C("410718794473946326084931838541824"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:L}"), val), string_type(C("410,718,794,473,946,326,084,931,838,541,824"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:Ld}"), val), string_type(C("410,718,794,473,946,326,084,931,838,541,824"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:Lo}"), val), string_type(C("1,210,000,000,000,000,000,000,000,000,000,000,000"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:#Lo}"), val), string_type(C("01,210,000,000,000,000,000,000,000,000,000,000,000"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:Lx}"), val), string_type(C("1,440,000,000,000,000,000,000,000,000"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:#Lx}"), val), string_type(C("0x1,440,000,000,000,000,000,000,000,000"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:Lb}"), val), string_type(C("1,010,001,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000"))); + BOOST_CHECK_EQUAL(std::format(loc, C("{:#Lb}"), val), string_type(C("0b1,010,001,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000"))); + } + catch (...) + {} + + + val = 32; + BOOST_CHECK_EQUAL(std::format(C("{:c}"), val), string_type(C(" "))); + val = 100000; + BOOST_CHECK_THROW(std::format(C("{:c}"), val), std::format_error); +} + +#endif diff --git a/test/test_format_cpp_int.cpp b/test/test_format_cpp_int.cpp new file mode 100644 index 000000000..7bf428b91 --- /dev/null +++ b/test/test_format_cpp_int.cpp @@ -0,0 +1,16 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include "test_format.hpp" + +int main() +{ + test_integer(); + test_integer(); + test_integer(); + test_integer(); + return 0; +} diff --git a/test/test_format_mpz_int.cpp b/test/test_format_mpz_int.cpp new file mode 100644 index 000000000..1bb7c26ae --- /dev/null +++ b/test/test_format_mpz_int.cpp @@ -0,0 +1,14 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include "test_format.hpp" + + +int main() +{ + test_integer(); + return 0; +} diff --git a/test/test_format_tom_int.cpp b/test/test_format_tom_int.cpp new file mode 100644 index 000000000..fe6e4b121 --- /dev/null +++ b/test/test_format_tom_int.cpp @@ -0,0 +1,14 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2023 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include "test_format.hpp" + + +int main() +{ + test_integer(); + return 0; +}