diff --git a/include/boost/decimal.hpp b/include/boost/decimal.hpp index 4d5ea2afb..0f162d5c8 100644 --- a/include/boost/decimal.hpp +++ b/include/boost/decimal.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #if defined(__clang__) && !defined(__GNUC__) # pragma clang diagnostic pop diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp new file mode 100644 index 000000000..4879fb5f7 --- /dev/null +++ b/include/boost/decimal/format.hpp @@ -0,0 +1,158 @@ +// Copyright 2023 - 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_DECIMAL_FORMAT_HPP +#define BOOST_DECIMAL_FORMAT_HPP + +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include() && !defined(BOOST_DECIMAL_DISABLE_CLIB) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Default :g +// Fixed :f +// Scientific :3 +// Hex :a +// +// Capital letter for any of the above leads to all characters being uppercase + +namespace boost::decimal::detail { + +template +constexpr auto parse_impl(ParseContext& ctx) +{ + auto it {ctx.begin()}; + ++it; + int precision = 6; + boost::decimal::chars_format fmt = boost::decimal::chars_format::general; + bool is_upper = false; + int padding_digits = 0; + + if (it == ctx.end()) + { + return std::make_tuple(precision, fmt, is_upper, padding_digits, it); + } + + while (*it >= '0' && *it <= '9') + { + padding_digits = padding_digits * 10 + *it; + ++it; + } + + if (*it == ':') + { + ++it; + } + + if (*it == '.') + { + ++it; + precision = 0; + while (*it >= '0' && *it <= '9') + { + precision = precision * 10 + *it; + ++it; + } + + switch (*it) + { + case 'G': + is_upper = true; + [[fallthrough]]; + case 'g': + fmt = chars_format::general; + break; + + case 'F': + [[fallthrough]]; + case 'f': + fmt = chars_format::fixed; + break; + + case 'E': + is_upper = true; + [[fallthrough]]; + case 'e': + fmt = chars_format::scientific; + break; + + case 'A': + is_upper = true; + [[fallthrough]]; + case 'a': + fmt = chars_format::hex; + break; + + default: + throw std::format_error("Invalid format"); + } + ++it; + } + + if (*it != '}') + { + throw std::format_error("Invalid format"); + } + + return std::make_tuple(precision, fmt, is_upper, padding_digits, it); +}; + +} //namespace boost::decimal::detail + +template <> +struct std::formatter +{ + int precision; + boost::decimal::chars_format fmt; + bool is_upper; + int padding_digits; + + constexpr auto parse(const std::basic_format_parse_context& context) + { + const auto res {boost::decimal::detail::parse_impl(context)}; + + precision = std::get<0>(res); + fmt = std::get<1>(res); + is_upper = std::get<2>(res); + padding_digits = std::get<3>(res); + + return std::get<4>(res); + } + + template + auto format(const boost::decimal::decimal32& v, std::basic_format_context& context) const + { + auto&& out = context.out(); + char buffer[128U]; + const auto r = to_chars(buffer, buffer + sizeof(buffer), v, fmt, precision); + *r.ptr = '\0'; + std::string s(buffer); + + if (is_upper) + { + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::toupper(c); }); + } + + if (s.size() < static_cast(padding_digits)) + { + s.insert(s.begin(), static_cast(padding_digits) - s.size(), '0'); + } + + out = std::copy(s.begin(), s.end(), out); + return out; + } +}; + +#endif + +#endif //BOOST_DECIMAL_FORMAT_HPP diff --git a/test/Jamfile b/test/Jamfile index dec6a6d4a..c905d8dd3 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -87,6 +87,7 @@ run test_exp.cpp ; run test_expm1.cpp ; run test_fenv.cpp ; run test_float_conversion.cpp ; +run test_format.cpp ; run test_frexp_ldexp.cpp ; run test_from_chars.cpp ; run test_git_issue_266.cpp ; diff --git a/test/test_format.cpp b/test/test_format.cpp new file mode 100644 index 000000000..5ced64500 --- /dev/null +++ b/test/test_format.cpp @@ -0,0 +1,71 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +using namespace boost::decimal; + +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include() && !defined(BOOST_DECIMAL_DISABLE_CLIB) + +#include + +template +void test_general() +{ + BOOST_TEST_EQ(std::format("{}", T{1}), "1"); + BOOST_TEST_EQ(std::format("{}", T{10}), "10"); + BOOST_TEST_EQ(std::format("{}", T{100}), "100"); + BOOST_TEST_EQ(std::format("{}", T{1000}), "1000"); + BOOST_TEST_EQ(std::format("{}", T{10000}), "10000"); + BOOST_TEST_EQ(std::format("{}", T{210000}), "210000"); + BOOST_TEST_EQ(std::format("{}", T{2100000}), "2100000"); + BOOST_TEST_EQ(std::format("{}", T{21, 6, true}), "-2.1e+07"); + BOOST_TEST_EQ(std::format("{}", T{211, 6, true}), "-2.11e+08"); + BOOST_TEST_EQ(std::format("{}", T{2111, 6, true}), "-2.111e+09"); + + BOOST_TEST_EQ(std::format("{}", std::numeric_limits::infinity()), "inf"); + BOOST_TEST_EQ(std::format("{}", -std::numeric_limits::infinity()), "-inf"); + BOOST_TEST_EQ(std::format("{}", std::numeric_limits::quiet_NaN()), "nan"); + BOOST_TEST_EQ(std::format("{}", -std::numeric_limits::quiet_NaN()), "-nan(ind)"); + BOOST_TEST_EQ(std::format("{}", std::numeric_limits::signaling_NaN()), "nan(snan)"); + BOOST_TEST_EQ(std::format("{}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); + + BOOST_TEST_EQ(std::format("{:g}", T{1}), "1"); + BOOST_TEST_EQ(std::format("{:g}", T{10}), "10"); + BOOST_TEST_EQ(std::format("{:g}", T{100}), "100"); + BOOST_TEST_EQ(std::format("{:g}", T{1000}), "1000"); + BOOST_TEST_EQ(std::format("{:g}", T{10000}), "10000"); + BOOST_TEST_EQ(std::format("{:g}", T{210000}), "210000"); + BOOST_TEST_EQ(std::format("{:g}", T{2100000}), "2100000"); + BOOST_TEST_EQ(std::format("{:g}", T{21, 6, true}), "-2.1e+07"); + BOOST_TEST_EQ(std::format("{:g}", T{211, 6, true}), "-2.11e+08"); + BOOST_TEST_EQ(std::format("{:g}", T{2111, 6, true}), "-2.111e+09"); + + BOOST_TEST_EQ(std::format("{:g}", std::numeric_limits::infinity()), "inf"); + BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits::infinity()), "-inf"); + BOOST_TEST_EQ(std::format("{:g}", std::numeric_limits::quiet_NaN()), "nan"); + BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits::quiet_NaN()), "-nan(ind)"); + BOOST_TEST_EQ(std::format("{:g}", std::numeric_limits::signaling_NaN()), "nan(snan)"); + BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); +} + +int main() +{ + test_general(); + + //test(); + //test(); + + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif