Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for <format> #363

Merged
merged 33 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f6c1853
Add format specialization for all types
mborland Dec 13, 2023
da20e86
Add test set
mborland Dec 13, 2023
3ffbdf2
Get trivial example working on GCC-13
mborland Mar 15, 2024
09c230b
Fix macro logic
mborland Mar 15, 2024
60ab8c3
Add significantly more support to parser
mborland Mar 15, 2024
75af309
Fix up tests
mborland Mar 15, 2024
6353000
Add general format specifier to tests
mborland Mar 15, 2024
b514162
Fix variable shadowing
mborland Mar 25, 2024
f68f331
Fix availability guards
mborland Mar 25, 2024
f2b58ca
Fixes and passing test with gcc-14
mborland Jan 6, 2025
ae4b982
Simplify support detection
mborland Jan 6, 2025
73cb8b5
Add decimal64 support
mborland Jan 6, 2025
001aa5b
adjust testing for decimal64
mborland Jan 6, 2025
e0ce9a6
Disable tests that fail on clang
mborland Jan 6, 2025
d6364b5
Make a decimal concept template formatter
mborland Jan 6, 2025
6122194
Add testing of additional types
mborland Jan 6, 2025
3ae8bdf
Begin adding fixed format tests
mborland Jan 6, 2025
1de7dbc
Begin simplified parser with better understanding of the context
mborland Jan 6, 2025
5fbe238
Add parsing of precision argument
mborland Jan 6, 2025
0b3d4e1
Add fixed format tests with precision argument
mborland Jan 6, 2025
6a18d6f
Add clang 18 and 19
mborland Jan 7, 2025
007e5cc
Suppress warnings
mborland Jan 7, 2025
6ec3f63
Add additional fixed and non-finite tests
mborland Jan 7, 2025
81a3d47
Add appropriate handling for a padding character
mborland Jan 7, 2025
7981f6f
Add scientific and padding tests
mborland Jan 7, 2025
45f016d
Remove old impl
mborland Jan 7, 2025
2533965
Add simple hex formatting
mborland Jan 7, 2025
787e5dd
Add format to docs
mborland Jan 7, 2025
ff91013
Add format example
mborland Jan 7, 2025
ea3cc75
Fix typo for MSVC
mborland Jan 7, 2025
b5d3092
Improve coverage
mborland Jan 7, 2025
56554af
Ignore internal MSVC warning
mborland Jan 7, 2025
37a0c24
Update build requirements
mborland Jan 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions doc/decimal.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
63 changes: 63 additions & 0 deletions doc/decimal/format.adoc
Original file line number Diff line number Diff line change
@@ -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>

Format is supported when using C++20 and a compiler with appropriate support: GCC >= 13, Clang >= 18, MSVC >= 19.40

=== 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 <boost/decimal.hpp>
#include <format>
#include <iostream>

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;
}
----
37 changes: 37 additions & 0 deletions examples/format.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2025 Matt Borland
// 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 <algorithm> so we need to try and supress that everywhere
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4244)
#endif

#include <boost/decimal.hpp>
#include <iostream>

#ifdef BOOST_CRYPT_HAS_FORMAT_SUPPORT

#include <format>

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 << "<format> is unsupported" << std::endl;
return 0;
}

#endif
3 changes: 3 additions & 0 deletions examples/statistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
#include <fstream>
#include <sstream>

// 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"
Expand Down
1 change: 1 addition & 0 deletions include/boost/decimal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <boost/decimal/cfloat.hpp>
#include <boost/decimal/charconv.hpp>
#include <boost/decimal/detail/io.hpp>
#include <boost/decimal/format.hpp>
#include <boost/decimal/cstdio.hpp>
#include <boost/decimal/bid_conversion.hpp>
#include <boost/decimal/dpd_conversion.hpp>
Expand Down
163 changes: 163 additions & 0 deletions include/boost/decimal/format.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// 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

// Many compilers seem to have <format> 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__ >= 18) || (defined(_MSC_VER) && _MSC_VER >= 1940))

#define BOOST_CRYPT_HAS_FORMAT_SUPPORT

#include <boost/decimal/charconv.hpp>
#include <algorithm>
#include <format>
#include <iostream>
#include <iomanip>
#include <string>
#include <tuple>
#include <cctype>

// Default :g
// Fixed :f
// Scientific :e
// Hex :a
//
// Capital letter for any of the above leads to all characters being uppercase

namespace boost::decimal::detail {

template <typename ParseContext>
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;

// 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 == '.')
{
++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)
{
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;
// LCOV_EXCL_START
default:
throw std::format_error("Invalid format specifier");
// LCOV_EXCL_STOP
}
}

++it;

return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it);
}

} // Namespace boost::decimal::detail

namespace std {

template <boost::decimal::concepts::decimal_floating_point_type T>
struct formatter<T>
{
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 <typename FormatContext>
auto format(const T &v, FormatContext &ctx) const
{
auto out = ctx.out();
std::array<char, 128> buffer {};
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<std::size_t>(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<std::size_t>(padding_digits))
{
s.insert(s.begin(), static_cast<std::size_t>(padding_digits) - s.size(), ' ');
}

return std::copy(s.begin(), s.end(), out);
}
};

} // Namespace std

#endif

#endif //BOOST_DECIMAL_FORMAT_HPP
2 changes: 2 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
Expand Down Expand Up @@ -153,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 ;
Loading
Loading