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

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions include/boost/decimal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <boost/decimal/charconv.hpp>
#include <boost/decimal/type_traits.hpp>
#include <boost/decimal/detail/io.hpp>
#include <boost/decimal/format.hpp>

#if defined(__clang__) && !defined(__GNUC__)
# pragma clang diagnostic pop
Expand Down
158 changes: 158 additions & 0 deletions include/boost/decimal/format.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2023 - 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_DECIMAL_FORMAT_HPP
#define BOOST_DECIMAL_FORMAT_HPP

#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include(<format>) && !defined(BOOST_DECIMAL_DISABLE_CLIB)

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

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

namespace boost::decimal::detail {

template <typename ParseContext>
constexpr auto parse_impl(ParseContext& ctx)
{
auto it {ctx.begin()};
++it;
int precision = 6;
boost::decimal::chars_format fmt = boost::decimal::chars_format::general;
bool is_upper = false;
int padding_digits = 0;

if (it == ctx.end())
{
return std::make_tuple(precision, fmt, is_upper, padding_digits, it);
}

while (*it >= '0' && *it <= '9')
{
padding_digits = padding_digits * 10 + *it;
++it;
}

if (*it == ':')
{
++it;
}

if (*it == '.')
{
++it;
precision = 0;
while (*it >= '0' && *it <= '9')
{
precision = precision * 10 + *it;
++it;
}

switch (*it)
{
case 'G':
is_upper = true;
[[fallthrough]];
case 'g':
fmt = chars_format::general;
break;

case 'F':
[[fallthrough]];
case 'f':
fmt = chars_format::fixed;
break;

case 'E':
is_upper = true;
[[fallthrough]];
case 'e':
fmt = chars_format::scientific;
break;

case 'A':
is_upper = true;
[[fallthrough]];
case 'a':
fmt = chars_format::hex;
break;

default:
throw std::format_error("Invalid format");
}
++it;
}

if (*it != '}')
{
throw std::format_error("Invalid format");
}

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

} //namespace boost::decimal::detail

template <>
struct std::formatter<boost::decimal::decimal32>
{
int precision;
boost::decimal::chars_format fmt;
bool is_upper;
int padding_digits;

constexpr auto parse(const std::basic_format_parse_context<char>& context)
{
const auto res {boost::decimal::detail::parse_impl(context)};

precision = std::get<0>(res);
fmt = std::get<1>(res);
is_upper = std::get<2>(res);
padding_digits = std::get<3>(res);

return std::get<4>(res);
}

template <typename OutputIterator>
auto format(const boost::decimal::decimal32& v, std::basic_format_context<OutputIterator, char>& context) const
{
auto&& out = context.out();
char buffer[128U];
const auto r = to_chars(buffer, buffer + sizeof(buffer), v, fmt, precision);
*r.ptr = '\0';
std::string s(buffer);

if (is_upper)
{
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::toupper(c); });
}

if (s.size() < static_cast<std::size_t>(padding_digits))
{
s.insert(s.begin(), static_cast<std::size_t>(padding_digits) - s.size(), '0');
}

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

#endif

#endif //BOOST_DECIMAL_FORMAT_HPP
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ run test_exp.cpp ;
run test_expm1.cpp ;
run test_fenv.cpp ;
run test_float_conversion.cpp ;
run test_format.cpp ;
run test_frexp_ldexp.cpp ;
run test_from_chars.cpp ;
run test_git_issue_266.cpp ;
Expand Down
71 changes: 71 additions & 0 deletions test/test_format.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2023 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/decimal.hpp>
#include <boost/core/lightweight_test.hpp>

using namespace boost::decimal;

#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include(<format>) && !defined(BOOST_DECIMAL_DISABLE_CLIB)

#include <format>

template <BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T>
void test_general()
{
BOOST_TEST_EQ(std::format("{}", T{1}), "1");
BOOST_TEST_EQ(std::format("{}", T{10}), "10");
BOOST_TEST_EQ(std::format("{}", T{100}), "100");
BOOST_TEST_EQ(std::format("{}", T{1000}), "1000");
BOOST_TEST_EQ(std::format("{}", T{10000}), "10000");
BOOST_TEST_EQ(std::format("{}", T{210000}), "210000");
BOOST_TEST_EQ(std::format("{}", T{2100000}), "2100000");
BOOST_TEST_EQ(std::format("{}", T{21, 6, true}), "-2.1e+07");
BOOST_TEST_EQ(std::format("{}", T{211, 6, true}), "-2.11e+08");
BOOST_TEST_EQ(std::format("{}", T{2111, 6, true}), "-2.111e+09");

BOOST_TEST_EQ(std::format("{}", std::numeric_limits<T>::infinity()), "inf");
BOOST_TEST_EQ(std::format("{}", -std::numeric_limits<T>::infinity()), "-inf");
BOOST_TEST_EQ(std::format("{}", std::numeric_limits<T>::quiet_NaN()), "nan");
BOOST_TEST_EQ(std::format("{}", -std::numeric_limits<T>::quiet_NaN()), "-nan(ind)");
BOOST_TEST_EQ(std::format("{}", std::numeric_limits<T>::signaling_NaN()), "nan(snan)");
BOOST_TEST_EQ(std::format("{}", -std::numeric_limits<T>::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<T>::infinity()), "inf");
BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits<T>::infinity()), "-inf");
BOOST_TEST_EQ(std::format("{:g}", std::numeric_limits<T>::quiet_NaN()), "nan");
BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits<T>::quiet_NaN()), "-nan(ind)");
BOOST_TEST_EQ(std::format("{:g}", std::numeric_limits<T>::signaling_NaN()), "nan(snan)");
BOOST_TEST_EQ(std::format("{:g}", -std::numeric_limits<T>::signaling_NaN()), "-nan(snan)");
}

int main()
{
test_general<decimal32>();

//test<decimal64>();
//test<decimal128>();

return boost::report_errors();
}

#else

int main()
{
return 0;
}

#endif
Loading