From f6c18539e447af3a9996a4c15f4ec5b92acb9017 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 13 Dec 2023 10:47:13 +0100 Subject: [PATCH 01/33] Add format specialization for all types --- include/boost/decimal/format.hpp | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 include/boost/decimal/format.hpp diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp new file mode 100644 index 000000000..4e8ed3a65 --- /dev/null +++ b/include/boost/decimal/format.hpp @@ -0,0 +1,133 @@ +// Copyright 2023 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 + +// See for general impl +// https://en.cppreference.com/w/cpp/utility/format/formatter + +namespace boost::decimal::detail { + +template +constexpr auto parse_impl(ParseContext& ctx) +{ + auto it {ctx.begin()}; + int precision = 5; + + if (it == ctx.end()) + { + return std::make_tuple(precision, it); + } + + if (*it == '.') + { + ++it; + precision = 0; + while (*it >= '0' && *it <= '9') + { + precision = precision * 10 + *it; + ++it; + } + + if (*it != 'f') + { + throw std::format_error("Invalid format"); + } + ++it; + } + + if (*it != '}') + { + throw std::format_error("Invalid format"); + } + + return std::make_tuple(precision, it); +}; +}; + +template <> +struct std::formatter +{ + int precision {}; + + template + constexpr auto parse(ParseContext& ctx) + { + auto res {boost::decimal::detail::parse_impl(ctx)}; + precision = std::get<0>(res); + return std::get<1>(res); + } + + template + auto format(const boost::decimal::decimal32& v, FormatContext& ctx) + { + std::ostringstream out; + out << std::setprecision(precision) << v; + + return std::ranges::copy(std::move(out).str(), ctx.out()).out; + } +}; + +template <> +struct std::formatter +{ + int precision; + + template + constexpr auto parse(ParseContext& ctx) + { + auto res {boost::decimal::detail::parse_impl(ctx)}; + precision = std::get<0>(res); + return std::get<1>(res); + } + + template + auto format(const boost::decimal::decimal32& v, FormatContext& ctx) + { + std::ostringstream out; + out << std::setprecision(precision) << v; + + return std::ranges::copy(std::move(out).str(), ctx.out()).out; + } +}; + +template <> +struct std::formatter +{ + int precision; + + template + constexpr auto parse(ParseContext& ctx) + { + auto res {boost::decimal::detail::parse_impl(ctx)}; + precision = std::get<0>(res); + return std::get<1>(res); + } + + template + auto format(const boost::decimal::decimal32& v, FormatContext& ctx) + { + std::ostringstream out; + out << std::setprecision(precision) << v; + + return std::ranges::copy(std::move(out).str(), ctx.out()).out; + } +}; + + +#endif + +#endif //BOOST_DECIMAL_FORMAT_HPP From da20e86bc07abc110253ee73815bac24813d5c3a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 13 Dec 2023 10:47:22 +0100 Subject: [PATCH 02/33] Add test set --- include/boost/decimal.hpp | 1 + test/Jamfile | 1 + test/test_format.cpp | 59 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 test/test_format.cpp diff --git a/include/boost/decimal.hpp b/include/boost/decimal.hpp index b878eb032..6152402a8 100644 --- a/include/boost/decimal.hpp +++ b/include/boost/decimal.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include diff --git a/test/Jamfile b/test/Jamfile index c7b1bd08a..f25a24521 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -111,6 +111,7 @@ run test_fast_math.cpp ; run test_fenv.cpp ; run test_fixed_width_trunc.cpp ; run test_float_conversion.cpp ; +run test_format.cpp ; run-fail test_fprintf.cpp ; run test_frexp_ldexp.cpp ; run test_from_chars.cpp /boost/charconv//boost_charconv ; diff --git a/test/test_format.cpp b/test/test_format.cpp new file mode 100644 index 000000000..ef496a66b --- /dev/null +++ b/test/test_format.cpp @@ -0,0 +1,59 @@ +// 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() +{ +/* + BOOST_TEST_EQ(std::format("{}", T{1}), "1"); + BOOST_TEST_EQ(std::format("{}", T{10}), "1e+01"); + BOOST_TEST_EQ(std::format("{}", T{100}), "1e+02"); + BOOST_TEST_EQ(std::format("{}", T{1000}), "1e+03"); + BOOST_TEST_EQ(std::format("{}", T{10000}), "1e+04"); + BOOST_TEST_EQ(std::format("{}", T{210000}), "2.1e+05"); + BOOST_TEST_EQ(std::format("{}", T{2100000}), "2.1e+06"); + 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)"); + */ + + constexpr const char* fmt_string = "{}"; + + BOOST_TEST_EQ(std::format(fmt_string, 1.0), "1"); + BOOST_TEST_EQ(std::format(fmt_string, T{1}), "1"); +} + +int main() +{ + test(); + //test(); + //test(); + + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif From 3ffbdf25df1f873684d6b5c475d3fde4c079f878 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 15 Mar 2024 11:12:50 +0100 Subject: [PATCH 03/33] Get trivial example working on GCC-13 --- include/boost/decimal/format.hpp | 89 ++++++++++---------------------- test/test_format.cpp | 5 +- 2 files changed, 27 insertions(+), 67 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 4e8ed3a65..d10c0d968 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Matt Borland +// Copyright 2023 - 2024 Matt Borland // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt @@ -10,14 +10,20 @@ #include #include #include +#include +#include #include #include #include #include #include -// See for general impl -// https://en.cppreference.com/w/cpp/utility/format/formatter +// 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 { @@ -25,11 +31,12 @@ template constexpr auto parse_impl(ParseContext& ctx) { auto it {ctx.begin()}; - int precision = 5; + int precision = 6; + boost::decimal::chars_format fmt = boost::decimal::chars_format::general; if (it == ctx.end()) { - return std::make_tuple(precision, it); + return std::make_tuple(precision, fmt, it); } if (*it == '.') @@ -54,80 +61,36 @@ constexpr auto parse_impl(ParseContext& ctx) throw std::format_error("Invalid format"); } - return std::make_tuple(precision, it); + return std::make_tuple(precision, fmt, it); }; }; template <> struct std::formatter -{ - int precision {}; - - template - constexpr auto parse(ParseContext& ctx) - { - auto res {boost::decimal::detail::parse_impl(ctx)}; - precision = std::get<0>(res); - return std::get<1>(res); - } - - template - auto format(const boost::decimal::decimal32& v, FormatContext& ctx) - { - std::ostringstream out; - out << std::setprecision(precision) << v; - - return std::ranges::copy(std::move(out).str(), ctx.out()).out; - } -}; - -template <> -struct std::formatter { int precision; + boost::decimal::chars_format fmt; - template - constexpr auto parse(ParseContext& ctx) + constexpr auto parse(const std::basic_format_parse_context& context) { - auto res {boost::decimal::detail::parse_impl(ctx)}; + auto res {boost::decimal::detail::parse_impl(context)}; precision = std::get<0>(res); - return std::get<1>(res); + fmt = std::get<1>(res); + return std::get<2>(res); } - template - auto format(const boost::decimal::decimal32& v, FormatContext& ctx) + template + auto format(const boost::decimal::decimal32& v, std::basic_format_context& context) const { - std::ostringstream out; - out << std::setprecision(precision) << v; - - return std::ranges::copy(std::move(out).str(), ctx.out()).out; + auto&& out = context.out(); + char buffer[128U]; + const auto r = to_chars(buffer, buffer + sizeof(buffer), v, fmt, precision); + *r.ptr = '\0'; + out = std::copy(buffer, r.ptr, out); + return out; } }; -template <> -struct std::formatter -{ - int precision; - - template - constexpr auto parse(ParseContext& ctx) - { - auto res {boost::decimal::detail::parse_impl(ctx)}; - precision = std::get<0>(res); - return std::get<1>(res); - } - - template - auto format(const boost::decimal::decimal32& v, FormatContext& ctx) - { - std::ostringstream out; - out << std::setprecision(precision) << v; - - return std::ranges::copy(std::move(out).str(), ctx.out()).out; - } -}; - - #endif #endif //BOOST_DECIMAL_FORMAT_HPP diff --git a/test/test_format.cpp b/test/test_format.cpp index ef496a66b..f793f5292 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -34,10 +34,7 @@ void test() BOOST_TEST_EQ(std::format("{}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); */ - constexpr const char* fmt_string = "{}"; - - BOOST_TEST_EQ(std::format(fmt_string, 1.0), "1"); - BOOST_TEST_EQ(std::format(fmt_string, T{1}), "1"); + BOOST_TEST_EQ(std::format("{}", T{1}), "1"); } int main() From 09c230b0378f0796510db0ba67aa0573af2330ce Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 15 Mar 2024 11:41:22 +0100 Subject: [PATCH 04/33] Fix macro logic --- include/boost/decimal/format.hpp | 2 +- test/test_format.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index d10c0d968..500c5c385 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -5,7 +5,7 @@ #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) +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include() && !defined(BOOST_DECIMAL_DISABLE_CLIB) #include #include diff --git a/test/test_format.cpp b/test/test_format.cpp index f793f5292..b2b6c9632 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -7,7 +7,7 @@ using namespace boost::decimal; -#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) && __has_include() && !defined(BOOST_DECIMAL_DISABLE_CLIB) +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include() && !defined(BOOST_DECIMAL_DISABLE_CLIB) #include From 60ab8c32479d34490448fc0179a383c881c0da5f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 15 Mar 2024 12:07:34 +0100 Subject: [PATCH 05/33] Add significantly more support to parser --- include/boost/decimal/format.hpp | 78 ++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 500c5c385..4879fb5f7 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -17,6 +17,7 @@ #include #include #include +#include // Default :g // Fixed :f @@ -31,12 +32,26 @@ 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, it); + 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 == '.') @@ -49,9 +64,37 @@ constexpr auto parse_impl(ParseContext& ctx) ++it; } - if (*it != 'f') + switch (*it) { - throw std::format_error("Invalid format"); + 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; } @@ -61,22 +104,29 @@ constexpr auto parse_impl(ParseContext& ctx) throw std::format_error("Invalid format"); } - return std::make_tuple(precision, fmt, it); -}; + 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) { - auto res {boost::decimal::detail::parse_impl(context)}; + const auto res {boost::decimal::detail::parse_impl(context)}; + precision = std::get<0>(res); fmt = std::get<1>(res); - return std::get<2>(res); + is_upper = std::get<2>(res); + padding_digits = std::get<3>(res); + + return std::get<4>(res); } template @@ -86,7 +136,19 @@ struct std::formatter char buffer[128U]; const auto r = to_chars(buffer, buffer + sizeof(buffer), v, fmt, precision); *r.ptr = '\0'; - out = std::copy(buffer, r.ptr, out); + 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; } }; From 75af309a6575f95335d72ca797ab1d79e765384a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 15 Mar 2024 12:08:16 +0100 Subject: [PATCH 06/33] Fix up tests --- test/test_format.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/test_format.cpp b/test/test_format.cpp index b2b6c9632..77ab92382 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -12,16 +12,15 @@ using namespace boost::decimal; #include template -void test() +void test_general() { -/* BOOST_TEST_EQ(std::format("{}", T{1}), "1"); - BOOST_TEST_EQ(std::format("{}", T{10}), "1e+01"); - BOOST_TEST_EQ(std::format("{}", T{100}), "1e+02"); - BOOST_TEST_EQ(std::format("{}", T{1000}), "1e+03"); - BOOST_TEST_EQ(std::format("{}", T{10000}), "1e+04"); - BOOST_TEST_EQ(std::format("{}", T{210000}), "2.1e+05"); - BOOST_TEST_EQ(std::format("{}", T{2100000}), "2.1e+06"); + 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"); @@ -32,14 +31,12 @@ void test() 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("{}", T{1}), "1"); } int main() { - test(); + test_general(); + //test(); //test(); From 6353000d17d5daf7e1067a644ef58834c38c8ea2 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 15 Mar 2024 12:12:56 +0100 Subject: [PATCH 07/33] Add general format specifier to tests --- test/test_format.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index 77ab92382..5ced64500 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -31,6 +31,24 @@ void test_general() 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() From b514162470c2d84e4509699cded234566e804ab1 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 25 Mar 2024 08:51:15 +0100 Subject: [PATCH 08/33] Fix variable shadowing --- include/boost/decimal/format.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 4879fb5f7..d2f446a4d 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -33,14 +33,14 @@ constexpr auto parse_impl(ParseContext& ctx) { auto it {ctx.begin()}; ++it; - int precision = 6; + int ctx_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); + return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it); } while (*it >= '0' && *it <= '9') @@ -57,10 +57,10 @@ constexpr auto parse_impl(ParseContext& ctx) if (*it == '.') { ++it; - precision = 0; + ctx_precision = 0; while (*it >= '0' && *it <= '9') { - precision = precision * 10 + *it; + ctx_precision = ctx_precision * 10 + *it; ++it; } @@ -104,7 +104,7 @@ constexpr auto parse_impl(ParseContext& ctx) throw std::format_error("Invalid format"); } - return std::make_tuple(precision, fmt, is_upper, padding_digits, it); + return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it); }; } //namespace boost::decimal::detail @@ -112,7 +112,7 @@ constexpr auto parse_impl(ParseContext& ctx) template <> struct std::formatter { - int precision; + int ctx_precision; boost::decimal::chars_format fmt; bool is_upper; int padding_digits; @@ -121,7 +121,7 @@ struct std::formatter { const auto res {boost::decimal::detail::parse_impl(context)}; - precision = std::get<0>(res); + ctx_precision = std::get<0>(res); fmt = std::get<1>(res); is_upper = std::get<2>(res); padding_digits = std::get<3>(res); @@ -134,7 +134,7 @@ struct std::formatter { auto&& out = context.out(); char buffer[128U]; - const auto r = to_chars(buffer, buffer + sizeof(buffer), v, fmt, precision); + const auto r = to_chars(buffer, buffer + sizeof(buffer), v, fmt, ctx_precision); *r.ptr = '\0'; std::string s(buffer); From f68f331f863b6b6a321645503a08513fd80b6f3b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 25 Mar 2024 09:04:36 +0100 Subject: [PATCH 09/33] Fix availability guards --- include/boost/decimal/format.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index d2f446a4d..a1ffcc620 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -5,7 +5,9 @@ #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) +// Many compilers seem to have with completly broken support so narrow down our support range +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && !defined(BOOST_DECIMAL_DISABLE_CLIB) && \ + ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 17) || (defined(_MSC_VER) && _MSC_VER >= 1930)) #include #include From f2b58caed06f15fb3466a9b525a2d7209235a5a8 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 14:56:44 -0500 Subject: [PATCH 10/33] Fixes and passing test with gcc-14 --- include/boost/decimal/format.hpp | 64 ++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index a1ffcc620..990c887bd 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -31,7 +31,7 @@ namespace boost::decimal::detail { template -constexpr auto parse_impl(ParseContext& ctx) +constexpr auto parse_impl(ParseContext &ctx) { auto it {ctx.begin()}; ++it; @@ -45,27 +45,32 @@ constexpr auto parse_impl(ParseContext& ctx) return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it); } - while (*it >= '0' && *it <= '9') + while (it != ctx.end() && *it >= '0' && *it <= '9') { - padding_digits = padding_digits * 10 + *it; + padding_digits = padding_digits * 10 + (*it - '0'); ++it; } - if (*it == ':') + if (it != ctx.end() && *it == ':') { ++it; } - if (*it == '.') + if (it != ctx.end() && *it == '.') { ++it; ctx_precision = 0; - while (*it >= '0' && *it <= '9') + while (it != ctx.end() && *it >= '0' && *it <= '9') { - ctx_precision = ctx_precision * 10 + *it; + ctx_precision = ctx_precision * 10 + (*it - '0'); ++it; } + if (it == ctx.end()) + { + throw std::format_error("Unexpected end of format string"); + } + switch (*it) { case 'G': @@ -101,27 +106,34 @@ constexpr auto parse_impl(ParseContext& ctx) ++it; } - if (*it != '}') + if (it == ctx.end() || *it != '}') { throw std::format_error("Invalid format"); } return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it); -}; +} + +} // Namespace boost::decimal::detail -} //namespace boost::decimal::detail +namespace std { template <> -struct std::formatter -{ +struct formatter { + constexpr formatter() : ctx_precision(6), + fmt(boost::decimal::chars_format::general), + is_upper(false), + padding_digits(0) + {} + int ctx_precision; boost::decimal::chars_format fmt; bool is_upper; int padding_digits; - constexpr auto parse(const std::basic_format_parse_context& context) + constexpr auto parse(format_parse_context &ctx) { - const auto res {boost::decimal::detail::parse_impl(context)}; + const auto res {boost::decimal::detail::parse_impl(ctx)}; ctx_precision = std::get<0>(res); fmt = std::get<1>(res); @@ -131,18 +143,21 @@ struct std::formatter return std::get<4>(res); } - template - auto format(const boost::decimal::decimal32& v, std::basic_format_context& context) const + template + auto format(const boost::decimal::decimal32 &v, FormatContext &ctx) const { - auto&& out = context.out(); - char buffer[128U]; - const auto r = to_chars(buffer, buffer + sizeof(buffer), v, fmt, ctx_precision); - *r.ptr = '\0'; - std::string s(buffer); + auto out = ctx.out(); + std::array buffer {}; + const auto r = to_chars(buffer.data(), buffer.data() + buffer.size(), v, fmt, ctx_precision); + + std::string_view sv(buffer.data(), static_cast(r.ptr - buffer.data())); + std::string s(sv); if (is_upper) { - std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::toupper(c); }); + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) + { return std::toupper(c); }); } if (s.size() < static_cast(padding_digits)) @@ -150,11 +165,12 @@ struct std::formatter s.insert(s.begin(), static_cast(padding_digits) - s.size(), '0'); } - out = std::copy(s.begin(), s.end(), out); - return out; + return std::copy(s.begin(), s.end(), out); } }; +} // Namespace std + #endif #endif //BOOST_DECIMAL_FORMAT_HPP From ae4b9829a6aa8bf4062bd169c16f547f64bbd4d4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 15:05:40 -0500 Subject: [PATCH 11/33] Simplify support detection --- include/boost/decimal/format.hpp | 4 +++- test/test_format.cpp | 7 +------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 990c887bd..5d3d79ba6 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -7,7 +7,9 @@ // Many compilers seem to have with completly broken support so narrow down our support range #if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && !defined(BOOST_DECIMAL_DISABLE_CLIB) && \ - ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 17) || (defined(_MSC_VER) && _MSC_VER >= 1930)) + ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ > 19) || (defined(_MSC_VER) && _MSC_VER >= 1930)) + +#define BOOST_CRYPT_HAS_FORMAT_SUPPORT #include #include diff --git a/test/test_format.cpp b/test/test_format.cpp index 5ced64500..eb2fb2d58 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -7,9 +7,7 @@ using namespace boost::decimal; -#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include() && !defined(BOOST_DECIMAL_DISABLE_CLIB) - -#include +#ifdef BOOST_CRYPT_HAS_FORMAT_SUPPORT template void test_general() @@ -55,9 +53,6 @@ int main() { test_general(); - //test(); - //test(); - return boost::report_errors(); } From 73cb8b5a28f61621493cf110553ed9076db88da4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 15:27:48 -0500 Subject: [PATCH 12/33] Add decimal64 support --- include/boost/decimal/format.hpp | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 5d3d79ba6..449863098 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -171,6 +171,57 @@ struct formatter { } }; +template <> +struct formatter { + constexpr formatter() : ctx_precision(6), + fmt(boost::decimal::chars_format::general), + is_upper(false), + padding_digits(0) + {} + + int ctx_precision; + boost::decimal::chars_format fmt; + bool is_upper; + int padding_digits; + + constexpr auto parse(format_parse_context &ctx) + { + const auto res {boost::decimal::detail::parse_impl(ctx)}; + + ctx_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::decimal64& v, FormatContext &ctx) const + { + auto out = ctx.out(); + std::array buffer {}; + const auto r = to_chars(buffer.data(), buffer.data() + buffer.size(), v, fmt, ctx_precision); + + std::string_view sv(buffer.data(), static_cast(r.ptr - buffer.data())); + std::string s(sv); + + 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'); + } + + return std::copy(s.begin(), s.end(), out); + } +}; + } // Namespace std #endif From 001aa5ba3102107cb8d81e00b4368ee4f42433f9 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 15:27:56 -0500 Subject: [PATCH 13/33] adjust testing for decimal64 --- test/test_format.cpp | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/test/test_format.cpp b/test/test_format.cpp index eb2fb2d58..63cda0347 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace boost::decimal; @@ -19,9 +20,19 @@ void test_general() 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"); + + if constexpr (std::numeric_limits::digits10 <= 7) + { + BOOST_TEST_EQ(std::format("{}", T {21, 6}), "2.1e+07"); + BOOST_TEST_EQ(std::format("{}", T {211, 6}), "2.11e+08"); + BOOST_TEST_EQ(std::format("{}", T {2111, 6}), "2.111e+09"); + } + else + { + BOOST_TEST_EQ(std::format("{}", T {21, 6}), "21000000"); + BOOST_TEST_EQ(std::format("{}", T {211, 6}), "211000000"); + BOOST_TEST_EQ(std::format("{}", T {2111, 6}), "2111000000"); + } BOOST_TEST_EQ(std::format("{}", std::numeric_limits::infinity()), "inf"); BOOST_TEST_EQ(std::format("{}", -std::numeric_limits::infinity()), "-inf"); @@ -37,9 +48,19 @@ void test_general() 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"); + + if constexpr (std::numeric_limits::digits10 <= 7) + { + 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"); + } + else + { + BOOST_TEST_EQ(std::format("{:g}", T {21, 6, true}), "-21000000"); + BOOST_TEST_EQ(std::format("{:g}", T {211, 6, true}), "-211000000"); + BOOST_TEST_EQ(std::format("{:g}", T {2111, 6, true}), "-2111000000"); + } BOOST_TEST_EQ(std::format("{:g}", std::numeric_limits::infinity()), "inf"); BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits::infinity()), "-inf"); @@ -52,6 +73,7 @@ void test_general() int main() { test_general(); + test_general(); return boost::report_errors(); } From e0ce9a6aee5324c6ae6076095925692f72476c87 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 15:46:00 -0500 Subject: [PATCH 14/33] Disable tests that fail on clang --- include/boost/decimal/format.hpp | 2 +- test/test_format.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 449863098..38fbbad5b 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -7,7 +7,7 @@ // Many compilers seem to have with completly broken support so narrow down our support range #if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && !defined(BOOST_DECIMAL_DISABLE_CLIB) && \ - ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ > 19) || (defined(_MSC_VER) && _MSC_VER >= 1930)) + ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 17) || (defined(_MSC_VER) && _MSC_VER >= 1930)) #define BOOST_CRYPT_HAS_FORMAT_SUPPORT diff --git a/test/test_format.cpp b/test/test_format.cpp index 63cda0347..d8ca4e4c4 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -13,6 +13,9 @@ using namespace boost::decimal; template void test_general() { + // For unknown reasons Clang does not like this empty bracket and throws compiler errors + #ifndef __clang__ + BOOST_TEST_EQ(std::format("{}", T{1}), "1"); BOOST_TEST_EQ(std::format("{}", T{10}), "10"); BOOST_TEST_EQ(std::format("{}", T{100}), "100"); @@ -41,6 +44,8 @@ void test_general() BOOST_TEST_EQ(std::format("{}", std::numeric_limits::signaling_NaN()), "nan(snan)"); BOOST_TEST_EQ(std::format("{}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); + #endif // defined(__clang__) + 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"); From d6364b51fa7a61d7907e72c3345f533d46bfe502 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 15:54:01 -0500 Subject: [PATCH 15/33] Make a decimal concept template formatter --- include/boost/decimal/format.hpp | 65 +++----------------------------- 1 file changed, 6 insertions(+), 59 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 38fbbad5b..7aa9abd2e 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -11,9 +11,6 @@ #define BOOST_CRYPT_HAS_FORMAT_SUPPORT -#include -#include -#include #include #include #include @@ -25,7 +22,7 @@ // Default :g // Fixed :f -// Scientific :3 +// Scientific :e // Hex :a // // Capital letter for any of the above leads to all characters being uppercase @@ -120,59 +117,9 @@ constexpr auto parse_impl(ParseContext &ctx) namespace std { -template <> -struct formatter { - constexpr formatter() : ctx_precision(6), - fmt(boost::decimal::chars_format::general), - is_upper(false), - padding_digits(0) - {} - - int ctx_precision; - boost::decimal::chars_format fmt; - bool is_upper; - int padding_digits; - - constexpr auto parse(format_parse_context &ctx) - { - const auto res {boost::decimal::detail::parse_impl(ctx)}; - - ctx_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, FormatContext &ctx) const - { - auto out = ctx.out(); - std::array buffer {}; - const auto r = to_chars(buffer.data(), buffer.data() + buffer.size(), v, fmt, ctx_precision); - - std::string_view sv(buffer.data(), static_cast(r.ptr - buffer.data())); - std::string s(sv); - - 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'); - } - - return std::copy(s.begin(), s.end(), out); - } -}; - -template <> -struct formatter { +template +struct formatter +{ constexpr formatter() : ctx_precision(6), fmt(boost::decimal::chars_format::general), is_upper(false), @@ -197,11 +144,11 @@ struct formatter { } template - auto format(const boost::decimal::decimal64& v, FormatContext &ctx) const + auto format(const T &v, FormatContext &ctx) const { auto out = ctx.out(); std::array buffer {}; - const auto r = to_chars(buffer.data(), buffer.data() + buffer.size(), v, fmt, ctx_precision); + const auto r = boost::decimal::to_chars(buffer.data(), buffer.data() + buffer.size(), v, fmt, ctx_precision); std::string_view sv(buffer.data(), static_cast(r.ptr - buffer.data())); std::string s(sv); From 61221941d9c772aa5979873d208d195b248e9fd4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 15:54:13 -0500 Subject: [PATCH 16/33] Add testing of additional types --- test/test_format.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index d8ca4e4c4..25cfbef57 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -78,7 +78,11 @@ void test_general() int main() { test_general(); + test_general(); test_general(); + test_general(); + test_general(); + test_general(); return boost::report_errors(); } From 3ae8bdf1f3259b1a412740fecb3e40310f33f56e Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 16:26:07 -0500 Subject: [PATCH 17/33] Begin adding fixed format tests --- test/test_format.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index 25cfbef57..d87f0d97b 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -75,6 +75,14 @@ void test_general() BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); } +template +void test_fixed() +{ + BOOST_TEST_EQ(std::format("{:f}", T {21, 6, true}), "-21000000.000000"); + BOOST_TEST_EQ(std::format("{:f}", T {211, 6, true}), "-211000000.000000"); + BOOST_TEST_EQ(std::format("{:f}", T {2111, 6, true}), "-2111000000.000000"); +} + int main() { test_general(); @@ -84,6 +92,13 @@ int main() test_general(); test_general(); + test_fixed(); + test_fixed(); + test_fixed(); + test_fixed(); + test_fixed(); + test_fixed(); + return boost::report_errors(); } From 1de7dbc617ceea8abcbb1044efe712367a56337b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 16:26:31 -0500 Subject: [PATCH 18/33] Begin simplified parser with better understanding of the context --- include/boost/decimal/format.hpp | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 7aa9abd2e..667c92884 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -29,6 +29,7 @@ namespace boost::decimal::detail { +/* template constexpr auto parse_impl(ParseContext &ctx) { @@ -112,6 +113,57 @@ constexpr auto parse_impl(ParseContext &ctx) return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it); } +*/ + +template +constexpr auto parse_impl(ParseContext &ctx) +{ + auto it {ctx.begin()}; + int ctx_precision = 6; + boost::decimal::chars_format fmt = boost::decimal::chars_format::general; + bool is_upper = false; + int padding_digits = 0; + + if (*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; + + return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it); +} } // Namespace boost::decimal::detail From 5fbe238fa800551ea18a8ce248cbc2771bcf31f9 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 16:59:03 -0500 Subject: [PATCH 19/33] Add parsing of precision argument --- include/boost/decimal/format.hpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 667c92884..2a4589016 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -124,7 +124,20 @@ constexpr auto parse_impl(ParseContext &ctx) bool is_upper = false; int padding_digits = 0; - if (*it != '}') + // If there is a . then we need to capture the precision argument + if (*it == '.') + { + ++it; + ctx_precision = 0; + while (it != ctx.end() && *it >= '0' && *it <= '9') + { + ctx_precision = ctx_precision * 10 + (*it - '0'); + ++it; + } + } + + // Lastly we capture the format to include if it's upper case + if (it != ctx.end() && *it != '}') { switch (*it) { From 0b3d4e1af23876f0845e2a7112acb69105e7d86b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 6 Jan 2025 16:59:19 -0500 Subject: [PATCH 20/33] Add fixed format tests with precision argument --- test/test_format.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index d87f0d97b..ed2f7d6e5 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -81,6 +81,10 @@ void test_fixed() BOOST_TEST_EQ(std::format("{:f}", T {21, 6, true}), "-21000000.000000"); BOOST_TEST_EQ(std::format("{:f}", T {211, 6, true}), "-211000000.000000"); BOOST_TEST_EQ(std::format("{:f}", T {2111, 6, true}), "-2111000000.000000"); + + BOOST_TEST_EQ(std::format("{:.0f}", T {21, 6, true}), std::string{"-21000000"}); + BOOST_TEST_EQ(std::format("{:.0f}", T {211, 6, true}), std::string{"-211000000"}); + BOOST_TEST_EQ(std::format("{:.0f}", T {2111, 6, true}), std::string{"-2111000000"}); } int main() From 6a18d6fe046aca4a103267ec34164f6005222496 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 09:26:10 -0500 Subject: [PATCH 21/33] Add clang 18 and 19 --- .github/workflows/ci.yml | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 759722275..3be989b34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -226,18 +226,26 @@ jobs: - "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" source_keys: - "https://apt.llvm.org/llvm-snapshot.gpg.key" - - name: UBSAN - toolset: clang - compiler: clang++-14 + - toolset: clang + compiler: clang++-18 cxxstd: "03,11,14,17,20,2b" - cxxflags: -stdlib=libc++ - linkflags: -stdlib=libc++ - ubsan: 1 - os: ubuntu-22.04 + os: ubuntu-24.04 install: - - clang-14 - - libc++-14-dev - - libc++abi-14-dev + - clang-18 + sources: + - "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - toolset: clang + compiler: clang++-19 + cxxstd: "03,11,14,17,20,2b" + os: ubuntu-24.04 + install: + - clang-19 + sources: + - "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" - toolset: clang cxxstd: "03,11,14,17,20,2b" From 007e5cca713c4a933d5d92ee5bcac6c813620afd Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 09:31:50 -0500 Subject: [PATCH 22/33] Suppress warnings --- examples/statistics.cpp | 3 +++ include/boost/decimal/format.hpp | 2 +- test/test_format.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/statistics.cpp b/examples/statistics.cpp index 2239cb295..86949e145 100644 --- a/examples/statistics.cpp +++ b/examples/statistics.cpp @@ -11,10 +11,13 @@ #include #include +// Warning suppression for boost.math #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wfloat-equal" # pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wstring-conversion" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wfloat-equal" diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 2a4589016..43dd5df3c 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -7,7 +7,7 @@ // Many compilers seem to have with completly broken support so narrow down our support range #if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && !defined(BOOST_DECIMAL_DISABLE_CLIB) && \ - ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 17) || (defined(_MSC_VER) && _MSC_VER >= 1930)) + ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ > 17) || (defined(_MSC_VER) && _MSC_VER >= 1930)) #define BOOST_CRYPT_HAS_FORMAT_SUPPORT diff --git a/test/test_format.cpp b/test/test_format.cpp index ed2f7d6e5..bcaa8d3c3 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -2,6 +2,12 @@ // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt +// MSVC 14.3 has a conversion error in so we need to try and supress that everywhere +#ifdef _MSC_VER +# pragma warning(push) +# pragma wanning(disable : 4244) +#endif + #include #include #include From 6ec3f63bbc316145bfb7c218ab4924c05f580abe Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 09:41:37 -0500 Subject: [PATCH 23/33] Add additional fixed and non-finite tests --- test/test_format.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index bcaa8d3c3..e2cc535ac 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -91,6 +91,18 @@ void test_fixed() BOOST_TEST_EQ(std::format("{:.0f}", T {21, 6, true}), std::string{"-21000000"}); BOOST_TEST_EQ(std::format("{:.0f}", T {211, 6, true}), std::string{"-211000000"}); BOOST_TEST_EQ(std::format("{:.0f}", T {2111, 6, true}), std::string{"-2111000000"}); + + BOOST_TEST_EQ(std::format("{:.1f}", T {21, 6, true}), std::string{"-21000000.0"}); + BOOST_TEST_EQ(std::format("{:.1f}", T {211, 6, true}), std::string{"-211000000.0"}); + BOOST_TEST_EQ(std::format("{:.1f}", T {2111, 6, true}), std::string{"-2111000000.0"}); + + BOOST_TEST_EQ(std::format("{:.0f}", T {0}), "0"); + BOOST_TEST_EQ(std::format("{:f}", std::numeric_limits::infinity()), "inf"); + BOOST_TEST_EQ(std::format("{:f}", -std::numeric_limits::infinity()), "-inf"); + BOOST_TEST_EQ(std::format("{:f}", std::numeric_limits::quiet_NaN()), "nan"); + BOOST_TEST_EQ(std::format("{:f}", -std::numeric_limits::quiet_NaN()), "-nan(ind)"); + BOOST_TEST_EQ(std::format("{:f}", std::numeric_limits::signaling_NaN()), "nan(snan)"); + BOOST_TEST_EQ(std::format("{:f}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); } int main() From 81a3d47f8f11b45a6c9060b507fdaf5a588c763d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 10:32:23 -0500 Subject: [PATCH 24/33] Add appropriate handling for a padding character --- include/boost/decimal/format.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 43dd5df3c..32f78c852 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -124,6 +124,13 @@ constexpr auto parse_impl(ParseContext &ctx) bool is_upper = false; int padding_digits = 0; + // Check for a padding character + while (it != ctx.end() && *it >= '0' && *it <= '9') + { + padding_digits = padding_digits * 10 + (*it - '0'); + ++it; + } + // If there is a . then we need to capture the precision argument if (*it == '.') { @@ -227,7 +234,7 @@ struct formatter if (s.size() < static_cast(padding_digits)) { - s.insert(s.begin(), static_cast(padding_digits) - s.size(), '0'); + s.insert(s.begin(), static_cast(padding_digits) - s.size(), ' '); } return std::copy(s.begin(), s.end(), out); From 7981f6f0eb10ba70a388754b4769732f325a4e63 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 10:32:32 -0500 Subject: [PATCH 25/33] Add scientific and padding tests --- test/test_format.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index e2cc535ac..a5357295a 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -105,6 +105,30 @@ void test_fixed() BOOST_TEST_EQ(std::format("{:f}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); } +template +void test_scientific() +{ + BOOST_TEST_EQ(std::format("{:e}", T {21, 6, true}), "-2.100000e+07"); + BOOST_TEST_EQ(std::format("{:e}", T {211, 6, true}), "-2.110000e+08"); + BOOST_TEST_EQ(std::format("{:e}", T {2111, 6, true}), "-2.111000e+09"); + + BOOST_TEST_EQ(std::format("{:E}", T {21, 6, true}), "-2.100000E+07"); + BOOST_TEST_EQ(std::format("{:E}", T {211, 6, true}), "-2.110000E+08"); + BOOST_TEST_EQ(std::format("{:E}", T {2111, 6, true}), "-2.111000E+09"); + + BOOST_TEST_EQ(std::format("{:.0E}", T {0}), "0E+00"); + BOOST_TEST_EQ(std::format("{:e}", std::numeric_limits::infinity()), "inf"); + BOOST_TEST_EQ(std::format("{:e}", -std::numeric_limits::infinity()), "-inf"); + BOOST_TEST_EQ(std::format("{:e}", std::numeric_limits::quiet_NaN()), "nan"); + BOOST_TEST_EQ(std::format("{:e}", -std::numeric_limits::quiet_NaN()), "-nan(ind)"); + BOOST_TEST_EQ(std::format("{:e}", std::numeric_limits::signaling_NaN()), "nan(snan)"); + BOOST_TEST_EQ(std::format("{:e}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); + + // Padding to the front + BOOST_TEST_EQ(std::format("{:10.1E}", T {0}), " 0.0E+00"); + BOOST_TEST_EQ(std::format("{:10.3E}", T {0}), " 0.000E+00"); +} + int main() { test_general(); @@ -121,6 +145,13 @@ int main() test_fixed(); test_fixed(); + test_scientific(); + test_scientific(); + test_scientific(); + test_scientific(); + test_scientific(); + test_scientific(); + return boost::report_errors(); } From 45f016d33f4dcf9d3fb1156b5673d817ab777ed3 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 10:33:51 -0500 Subject: [PATCH 26/33] Remove old impl --- include/boost/decimal/format.hpp | 88 +------------------------------- 1 file changed, 1 insertion(+), 87 deletions(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 32f78c852..9315a2744 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -29,92 +29,6 @@ namespace boost::decimal::detail { -/* -template -constexpr auto parse_impl(ParseContext &ctx) -{ - auto it {ctx.begin()}; - ++it; - int ctx_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(ctx_precision, fmt, is_upper, padding_digits, it); - } - - while (it != ctx.end() && *it >= '0' && *it <= '9') - { - padding_digits = padding_digits * 10 + (*it - '0'); - ++it; - } - - if (it != ctx.end() && *it == ':') - { - ++it; - } - - if (it != ctx.end() && *it == '.') - { - ++it; - ctx_precision = 0; - while (it != ctx.end() && *it >= '0' && *it <= '9') - { - ctx_precision = ctx_precision * 10 + (*it - '0'); - ++it; - } - - if (it == ctx.end()) - { - throw std::format_error("Unexpected end of format string"); - } - - 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 == ctx.end() || *it != '}') - { - throw std::format_error("Invalid format"); - } - - return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it); -} -*/ - template constexpr auto parse_impl(ParseContext &ctx) { @@ -176,7 +90,7 @@ constexpr auto parse_impl(ParseContext &ctx) break; default: - throw std::format_error("Invalid format"); + throw std::format_error("Invalid format specifier"); } } From 2533965e281fcc86729a2381ca9b3d6f40aa3a75 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 10:42:20 -0500 Subject: [PATCH 27/33] Add simple hex formatting --- test/test_format.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/test_format.cpp b/test/test_format.cpp index a5357295a..8980d62c9 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -129,6 +129,19 @@ void test_scientific() BOOST_TEST_EQ(std::format("{:10.3E}", T {0}), " 0.000E+00"); } +template +void test_hex() +{ + BOOST_TEST_EQ(std::format("{:.0a}", T {0}), "0p+00"); + BOOST_TEST_EQ(std::format("{:.3A}", T {0}), "0.000P+00"); + BOOST_TEST_EQ(std::format("{:a}", std::numeric_limits::infinity()), "inf"); + BOOST_TEST_EQ(std::format("{:a}", -std::numeric_limits::infinity()), "-inf"); + BOOST_TEST_EQ(std::format("{:a}", std::numeric_limits::quiet_NaN()), "nan"); + BOOST_TEST_EQ(std::format("{:a}", -std::numeric_limits::quiet_NaN()), "-nan(ind)"); + BOOST_TEST_EQ(std::format("{:a}", std::numeric_limits::signaling_NaN()), "nan(snan)"); + BOOST_TEST_EQ(std::format("{:a}", -std::numeric_limits::signaling_NaN()), "-nan(snan)"); +} + int main() { test_general(); @@ -152,6 +165,13 @@ int main() test_scientific(); test_scientific(); + test_hex(); + test_hex(); + test_hex(); + test_hex(); + test_hex(); + test_hex(); + return boost::report_errors(); } From 787e5dd6e2a9b65712d3b77edb5e9653f87afe64 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 11:06:23 -0500 Subject: [PATCH 28/33] Add format to docs --- doc/decimal.adoc | 1 + doc/decimal/format.adoc | 63 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 doc/decimal/format.adoc diff --git a/doc/decimal.adoc b/doc/decimal.adoc index 085ee902e..b5e0dd5cb 100644 --- a/doc/decimal.adoc +++ b/doc/decimal.adoc @@ -33,6 +33,7 @@ include::decimal/numbers.adoc[] include::decimal/cmath.adoc[] include::decimal/cstdlib.adoc[] include::decimal/charconv.adoc[] +include::decimal/format.adoc[] include::decimal/cfenv.adoc[] include::decimal/cfloat.adoc[] include::decimal/cstdio.adoc[] diff --git a/doc/decimal/format.adoc b/doc/decimal/format.adoc new file mode 100644 index 000000000..087d1cfa1 --- /dev/null +++ b/doc/decimal/format.adoc @@ -0,0 +1,63 @@ +//// +Copyright 2025 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#format] += format support +:idprefix: format_ + +== + +Format is supported when using C++20 and a compiler with appropriate support: GCC >= 13, Clang >= 18, MSVC >= 19.30 + +=== Type Modifiers + +The following type modifiers are the same as those used by built-in floating point values: + +- "g" or "G" for general format +- "e" or "E" for scientific format +- "f" for fixed format +- "a" or "A" for hex format + +Example usage for scientific format would be: `{:e}` + +NOTE: The uppercase format will return with all applicable values in uppercase (e.g. 3.14E+02 vs 3.14e+02) + +=== Precision Modifiers + +Precision can be specified in the same way as built-in floating point values. +For example a scientific format with 3 digits or precision would be: `{:.3e}` + +=== Padding Modifiers + +If you want all values to be printed with a fixed width padding is allowed before the precision modifier. +For example with `{:10.3e}`: + +- 3.14 -> " 3.140e+00" +- 3.141 -> " 3.141e+00" + +Note the space at the front of these string to keep with width at 10 characters + +=== Examples + +The example is padding modifiers can be done like so + +[source, c++] +---- +#include +#include +#include + +int main() +{ + constexpr boost::decimal::decimal64 val1 {314, -2}; + constexpr boost::decimal::decimal32 val2 {3141, -3}; + + std::cout << std::format("{:10.3e}", val1) << '\n'; + std::cout << std::format("{:10.3e}", val2) << std::endl; + + return 0; +} +---- From ff910130f4a8e413c32977abc8521c6e93589134 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 11:13:56 -0500 Subject: [PATCH 29/33] Add format example --- examples/format.cpp | 31 +++++++++++++++++++++++++++++++ test/Jamfile | 1 + 2 files changed, 32 insertions(+) create mode 100644 examples/format.cpp diff --git a/examples/format.cpp b/examples/format.cpp new file mode 100644 index 000000000..14e34dd76 --- /dev/null +++ b/examples/format.cpp @@ -0,0 +1,31 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#ifdef BOOST_CRYPT_HAS_FORMAT_SUPPORT + +#include + +int main() +{ + constexpr boost::decimal::decimal64 val1 {314, -2}; + constexpr boost::decimal::decimal32 val2 {3141, -3}; + + std::cout << std::format("{:10.3e}", val1) << '\n'; + std::cout << std::format("{:10.3e}", val2) << std::endl; + + return 0; +} + +#else + +int main() +{ + std::cout << " is unsupported" << std::endl; + return 0; +} + +#endif diff --git a/test/Jamfile b/test/Jamfile index f25a24521..d67baeb4f 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -154,3 +154,4 @@ run ../examples/rounding_mode.cpp ; run ../examples/moving_average.cpp ; run ../examples/currency_conversion.cpp ; run ../examples/statistics.cpp ; +run ../examples/format.cpp ; From ea3cc7596519a3fa1cb133cfa789a0488449d5ff Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 11:15:44 -0500 Subject: [PATCH 30/33] Fix typo for MSVC --- test/test_format.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_format.cpp b/test/test_format.cpp index 8980d62c9..f3a1ca778 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -5,7 +5,7 @@ // MSVC 14.3 has a conversion error in so we need to try and supress that everywhere #ifdef _MSC_VER # pragma warning(push) -# pragma wanning(disable : 4244) +# pragma warning(disable : 4244) #endif #include From b5d30920d8116f8df3692ef4bcf785cbb851aee4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 11:32:26 -0500 Subject: [PATCH 31/33] Improve coverage --- include/boost/decimal/format.hpp | 3 ++- test/test_format.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 9315a2744..21b97d047 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -88,9 +88,10 @@ constexpr auto parse_impl(ParseContext &ctx) case 'a': fmt = chars_format::hex; break; - + // LCOV_EXCL_START default: throw std::format_error("Invalid format specifier"); + // LCOV_EXCL_STOP } } diff --git a/test/test_format.cpp b/test/test_format.cpp index f3a1ca778..b4cc97101 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -65,6 +65,9 @@ void test_general() 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}", 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"); } else { From 56554afb5bfdc2b4cba1ce3a05f7b9ebdff804a0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 11:49:24 -0500 Subject: [PATCH 32/33] Ignore internal MSVC warning --- examples/format.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/format.cpp b/examples/format.cpp index 14e34dd76..da14a682b 100644 --- a/examples/format.cpp +++ b/examples/format.cpp @@ -2,6 +2,12 @@ // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt +// MSVC 14.3 has a conversion error in so we need to try and supress that everywhere +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4244) +#endif + #include #include From 37a0c245211ae711342c93c78643b45ece56154f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Jan 2025 12:58:55 -0500 Subject: [PATCH 33/33] Update build requirements --- doc/decimal/format.adoc | 2 +- include/boost/decimal/format.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/decimal/format.adoc b/doc/decimal/format.adoc index 087d1cfa1..038fa7198 100644 --- a/doc/decimal/format.adoc +++ b/doc/decimal/format.adoc @@ -10,7 +10,7 @@ https://www.boost.org/LICENSE_1_0.txt == -Format is supported when using C++20 and a compiler with appropriate support: GCC >= 13, Clang >= 18, MSVC >= 19.30 +Format is supported when using C++20 and a compiler with appropriate support: GCC >= 13, Clang >= 18, MSVC >= 19.40 === Type Modifiers diff --git a/include/boost/decimal/format.hpp b/include/boost/decimal/format.hpp index 21b97d047..b72466929 100644 --- a/include/boost/decimal/format.hpp +++ b/include/boost/decimal/format.hpp @@ -7,7 +7,7 @@ // Many compilers seem to have with completly broken support so narrow down our support range #if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && !defined(BOOST_DECIMAL_DISABLE_CLIB) && \ - ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ > 17) || (defined(_MSC_VER) && _MSC_VER >= 1930)) + ((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 18) || (defined(_MSC_VER) && _MSC_VER >= 1940)) #define BOOST_CRYPT_HAS_FORMAT_SUPPORT