Skip to content

Commit

Permalink
Merge pull request #363 from cppalliance/format
Browse files Browse the repository at this point in the history
Support for <format>
  • Loading branch information
mborland authored Jan 7, 2025
2 parents 3f992fa + 37a0c24 commit 50b689c
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 10 deletions.
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

0 comments on commit 50b689c

Please sign in to comment.