Skip to content

Commit 50b689c

Browse files
authored
Merge pull request #363 from cppalliance/format
Support for <format>
2 parents 3f992fa + 37a0c24 commit 50b689c

File tree

9 files changed

+476
-10
lines changed

9 files changed

+476
-10
lines changed

.github/workflows/ci.yml

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,18 +226,26 @@ jobs:
226226
- "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main"
227227
source_keys:
228228
- "https://apt.llvm.org/llvm-snapshot.gpg.key"
229-
- name: UBSAN
230-
toolset: clang
231-
compiler: clang++-14
229+
- toolset: clang
230+
compiler: clang++-18
232231
cxxstd: "03,11,14,17,20,2b"
233-
cxxflags: -stdlib=libc++
234-
linkflags: -stdlib=libc++
235-
ubsan: 1
236-
os: ubuntu-22.04
232+
os: ubuntu-24.04
237233
install:
238-
- clang-14
239-
- libc++-14-dev
240-
- libc++abi-14-dev
234+
- clang-18
235+
sources:
236+
- "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main"
237+
source_keys:
238+
- "https://apt.llvm.org/llvm-snapshot.gpg.key"
239+
- toolset: clang
240+
compiler: clang++-19
241+
cxxstd: "03,11,14,17,20,2b"
242+
os: ubuntu-24.04
243+
install:
244+
- clang-19
245+
sources:
246+
- "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main"
247+
source_keys:
248+
- "https://apt.llvm.org/llvm-snapshot.gpg.key"
241249

242250
- toolset: clang
243251
cxxstd: "03,11,14,17,20,2b"

doc/decimal.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ include::decimal/numbers.adoc[]
3333
include::decimal/cmath.adoc[]
3434
include::decimal/cstdlib.adoc[]
3535
include::decimal/charconv.adoc[]
36+
include::decimal/format.adoc[]
3637
include::decimal/cfenv.adoc[]
3738
include::decimal/cfloat.adoc[]
3839
include::decimal/cstdio.adoc[]

doc/decimal/format.adoc

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
////
2+
Copyright 2025 Matt Borland
3+
Distributed under the Boost Software License, Version 1.0.
4+
https://www.boost.org/LICENSE_1_0.txt
5+
////
6+
7+
[#format]
8+
= format support
9+
:idprefix: format_
10+
11+
== <format>
12+
13+
Format is supported when using C++20 and a compiler with appropriate support: GCC >= 13, Clang >= 18, MSVC >= 19.40
14+
15+
=== Type Modifiers
16+
17+
The following type modifiers are the same as those used by built-in floating point values:
18+
19+
- "g" or "G" for general format
20+
- "e" or "E" for scientific format
21+
- "f" for fixed format
22+
- "a" or "A" for hex format
23+
24+
Example usage for scientific format would be: `{:e}`
25+
26+
NOTE: The uppercase format will return with all applicable values in uppercase (e.g. 3.14E+02 vs 3.14e+02)
27+
28+
=== Precision Modifiers
29+
30+
Precision can be specified in the same way as built-in floating point values.
31+
For example a scientific format with 3 digits or precision would be: `{:.3e}`
32+
33+
=== Padding Modifiers
34+
35+
If you want all values to be printed with a fixed width padding is allowed before the precision modifier.
36+
For example with `{:10.3e}`:
37+
38+
- 3.14 -> " 3.140e+00"
39+
- 3.141 -> " 3.141e+00"
40+
41+
Note the space at the front of these string to keep with width at 10 characters
42+
43+
=== Examples
44+
45+
The example is padding modifiers can be done like so
46+
47+
[source, c++]
48+
----
49+
#include <boost/decimal.hpp>
50+
#include <format>
51+
#include <iostream>
52+
53+
int main()
54+
{
55+
constexpr boost::decimal::decimal64 val1 {314, -2};
56+
constexpr boost::decimal::decimal32 val2 {3141, -3};
57+
58+
std::cout << std::format("{:10.3e}", val1) << '\n';
59+
std::cout << std::format("{:10.3e}", val2) << std::endl;
60+
61+
return 0;
62+
}
63+
----

examples/format.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2025 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
5+
// MSVC 14.3 has a conversion error in <algorithm> so we need to try and supress that everywhere
6+
#ifdef _MSC_VER
7+
# pragma warning(push)
8+
# pragma warning(disable : 4244)
9+
#endif
10+
11+
#include <boost/decimal.hpp>
12+
#include <iostream>
13+
14+
#ifdef BOOST_CRYPT_HAS_FORMAT_SUPPORT
15+
16+
#include <format>
17+
18+
int main()
19+
{
20+
constexpr boost::decimal::decimal64 val1 {314, -2};
21+
constexpr boost::decimal::decimal32 val2 {3141, -3};
22+
23+
std::cout << std::format("{:10.3e}", val1) << '\n';
24+
std::cout << std::format("{:10.3e}", val2) << std::endl;
25+
26+
return 0;
27+
}
28+
29+
#else
30+
31+
int main()
32+
{
33+
std::cout << "<format> is unsupported" << std::endl;
34+
return 0;
35+
}
36+
37+
#endif

examples/statistics.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
#include <fstream>
1212
#include <sstream>
1313

14+
// Warning suppression for boost.math
1415
#if defined(__clang__)
1516
# pragma clang diagnostic push
1617
# pragma clang diagnostic ignored "-Wfloat-equal"
1718
# pragma clang diagnostic ignored "-Wsign-conversion"
19+
# pragma clang diagnostic ignored "-Wundef"
20+
# pragma clang diagnostic ignored "-Wstring-conversion"
1821
#elif defined(__GNUC__)
1922
# pragma GCC diagnostic push
2023
# pragma GCC diagnostic ignored "-Wfloat-equal"

include/boost/decimal.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <boost/decimal/cfloat.hpp>
3939
#include <boost/decimal/charconv.hpp>
4040
#include <boost/decimal/detail/io.hpp>
41+
#include <boost/decimal/format.hpp>
4142
#include <boost/decimal/cstdio.hpp>
4243
#include <boost/decimal/bid_conversion.hpp>
4344
#include <boost/decimal/dpd_conversion.hpp>

include/boost/decimal/format.hpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2023 - 2024 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
5+
#ifndef BOOST_DECIMAL_FORMAT_HPP
6+
#define BOOST_DECIMAL_FORMAT_HPP
7+
8+
// Many compilers seem to have <format> with completly broken support so narrow down our support range
9+
#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && !defined(BOOST_DECIMAL_DISABLE_CLIB) && \
10+
((defined(__GNUC__) && __GNUC__ >= 13) || (defined(__clang__) && __clang_major__ >= 18) || (defined(_MSC_VER) && _MSC_VER >= 1940))
11+
12+
#define BOOST_CRYPT_HAS_FORMAT_SUPPORT
13+
14+
#include <boost/decimal/charconv.hpp>
15+
#include <algorithm>
16+
#include <format>
17+
#include <iostream>
18+
#include <iomanip>
19+
#include <string>
20+
#include <tuple>
21+
#include <cctype>
22+
23+
// Default :g
24+
// Fixed :f
25+
// Scientific :e
26+
// Hex :a
27+
//
28+
// Capital letter for any of the above leads to all characters being uppercase
29+
30+
namespace boost::decimal::detail {
31+
32+
template <typename ParseContext>
33+
constexpr auto parse_impl(ParseContext &ctx)
34+
{
35+
auto it {ctx.begin()};
36+
int ctx_precision = 6;
37+
boost::decimal::chars_format fmt = boost::decimal::chars_format::general;
38+
bool is_upper = false;
39+
int padding_digits = 0;
40+
41+
// Check for a padding character
42+
while (it != ctx.end() && *it >= '0' && *it <= '9')
43+
{
44+
padding_digits = padding_digits * 10 + (*it - '0');
45+
++it;
46+
}
47+
48+
// If there is a . then we need to capture the precision argument
49+
if (*it == '.')
50+
{
51+
++it;
52+
ctx_precision = 0;
53+
while (it != ctx.end() && *it >= '0' && *it <= '9')
54+
{
55+
ctx_precision = ctx_precision * 10 + (*it - '0');
56+
++it;
57+
}
58+
}
59+
60+
// Lastly we capture the format to include if it's upper case
61+
if (it != ctx.end() && *it != '}')
62+
{
63+
switch (*it)
64+
{
65+
case 'G':
66+
is_upper = true;
67+
[[fallthrough]];
68+
case 'g':
69+
fmt = chars_format::general;
70+
break;
71+
72+
case 'F':
73+
[[fallthrough]];
74+
case 'f':
75+
fmt = chars_format::fixed;
76+
break;
77+
78+
case 'E':
79+
is_upper = true;
80+
[[fallthrough]];
81+
case 'e':
82+
fmt = chars_format::scientific;
83+
break;
84+
85+
case 'A':
86+
is_upper = true;
87+
[[fallthrough]];
88+
case 'a':
89+
fmt = chars_format::hex;
90+
break;
91+
// LCOV_EXCL_START
92+
default:
93+
throw std::format_error("Invalid format specifier");
94+
// LCOV_EXCL_STOP
95+
}
96+
}
97+
98+
++it;
99+
100+
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, it);
101+
}
102+
103+
} // Namespace boost::decimal::detail
104+
105+
namespace std {
106+
107+
template <boost::decimal::concepts::decimal_floating_point_type T>
108+
struct formatter<T>
109+
{
110+
constexpr formatter() : ctx_precision(6),
111+
fmt(boost::decimal::chars_format::general),
112+
is_upper(false),
113+
padding_digits(0)
114+
{}
115+
116+
int ctx_precision;
117+
boost::decimal::chars_format fmt;
118+
bool is_upper;
119+
int padding_digits;
120+
121+
constexpr auto parse(format_parse_context &ctx)
122+
{
123+
const auto res {boost::decimal::detail::parse_impl(ctx)};
124+
125+
ctx_precision = std::get<0>(res);
126+
fmt = std::get<1>(res);
127+
is_upper = std::get<2>(res);
128+
padding_digits = std::get<3>(res);
129+
130+
return std::get<4>(res);
131+
}
132+
133+
template <typename FormatContext>
134+
auto format(const T &v, FormatContext &ctx) const
135+
{
136+
auto out = ctx.out();
137+
std::array<char, 128> buffer {};
138+
const auto r = boost::decimal::to_chars(buffer.data(), buffer.data() + buffer.size(), v, fmt, ctx_precision);
139+
140+
std::string_view sv(buffer.data(), static_cast<std::size_t>(r.ptr - buffer.data()));
141+
std::string s(sv);
142+
143+
if (is_upper)
144+
{
145+
std::transform(s.begin(), s.end(), s.begin(),
146+
[](unsigned char c)
147+
{ return std::toupper(c); });
148+
}
149+
150+
if (s.size() < static_cast<std::size_t>(padding_digits))
151+
{
152+
s.insert(s.begin(), static_cast<std::size_t>(padding_digits) - s.size(), ' ');
153+
}
154+
155+
return std::copy(s.begin(), s.end(), out);
156+
}
157+
};
158+
159+
} // Namespace std
160+
161+
#endif
162+
163+
#endif //BOOST_DECIMAL_FORMAT_HPP

test/Jamfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ run test_fast_math.cpp ;
111111
run test_fenv.cpp ;
112112
run test_fixed_width_trunc.cpp ;
113113
run test_float_conversion.cpp ;
114+
run test_format.cpp ;
114115
run-fail test_fprintf.cpp ;
115116
run test_frexp_ldexp.cpp ;
116117
run test_from_chars.cpp /boost/charconv//boost_charconv ;
@@ -153,3 +154,4 @@ run ../examples/rounding_mode.cpp ;
153154
run ../examples/moving_average.cpp ;
154155
run ../examples/currency_conversion.cpp ;
155156
run ../examples/statistics.cpp ;
157+
run ../examples/format.cpp ;

0 commit comments

Comments
 (0)