Skip to content

Commit

Permalink
Merge pull request #504 from cppalliance/printf
Browse files Browse the repository at this point in the history
Add fprintf and printf
  • Loading branch information
mborland authored Apr 24, 2024
2 parents 74b13a8 + 3b81bf6 commit e109d0e
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 16 deletions.
6 changes: 6 additions & 0 deletions doc/decimal/cstdio.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ int snprintf(char* buffer, std::size_t buf_size, const char* format, Dec... valu
template <typename... Dec>
int sprintf(char* buffer, const char* format, Dec... value) noexcept;
template <typename... Dec>
int fprintf(std::FILE* buffer, const char* format, Dec... values) noexcept;
template <typename... Dec>
int printf(const char* format, Dec... values) noexcept;
} //namespace decimal
} //namespace boost
----
Expand Down
130 changes: 114 additions & 16 deletions include/boost/decimal/cstdio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
#include <boost/decimal/detail/locale_conversion.hpp>
#include <boost/decimal/detail/parser.hpp>
#include <boost/decimal/detail/concepts.hpp>
#include <boost/decimal/detail/attributes.hpp>
#include <boost/decimal/charconv.hpp>

#ifndef BOOST_DECIMAL_BUILD_MODULE
#include <memory>
#include <new>
#include <cctype>
#include <cstdio>
#endif
Expand Down Expand Up @@ -47,8 +50,8 @@ inline auto parse_format(const char* format) -> parameters
// If the format is unspecified or incorrect we will use this as the default values
parameters params {6, chars_format::general, decimal_type::decimal64, false};

auto iter = format;
const auto last = format + std::strlen(format);
auto iter {format};
const auto last {format + std::strlen(format)};

if (iter == last || *iter != '%')
{
Expand Down Expand Up @@ -152,7 +155,7 @@ inline void make_uppercase(char* first, const char* last) noexcept
}

template <typename... T>
inline auto snprintf_impl(char* buffer, std::size_t buf_size, const char* format, parameters params, T... values) noexcept
inline auto snprintf_impl(char* buffer, std::size_t buf_size, const char* format, T... values) noexcept
#ifndef BOOST_DECIMAL_HAS_CONCEPTS
-> std::enable_if_t<detail::is_decimal_floating_point_v<std::common_type_t<T...>>, int>
#else
Expand All @@ -164,42 +167,78 @@ inline auto snprintf_impl(char* buffer, std::size_t buf_size, const char* format
return -1;
}

std::ptrdiff_t byte_count {};
for (const auto& value : {values...})
std::size_t byte_count {};
const std::initializer_list<std::common_type_t<T...>> values_list {values...};
auto value_iter = values_list.begin();
const char* iter {format};
const char* buffer_begin {buffer};
const char* buffer_end {buffer + buf_size};

const auto format_size {std::strlen(format)};

while (buffer < buffer_end && byte_count < format_size)
{
while (buffer < buffer_end && byte_count < format_size && *iter != '%')
{
*buffer++ = *iter++;
++byte_count;
}

if (byte_count == format_size || buffer == buffer_end)
{
break;
}

char params_buffer[10] {};
std::size_t param_iter {};
while (param_iter < 10U && byte_count < format_size && *iter != ' ' && *iter != '"')
{
params_buffer[param_iter] = *iter++;
++byte_count;
++param_iter;
}

const auto params = parse_format(params_buffer);
to_chars_result r;
switch (params.return_type)
{
// Subtract 1 from all cases to ensure there is room to insert the null terminator
case detail::decimal_type::decimal32:
r = to_chars(buffer + byte_count, buffer + buf_size - byte_count - 1, static_cast<decimal32>(value), params.fmt, params.precision);
r = to_chars(buffer, buffer + buf_size - byte_count, static_cast<decimal32>(*value_iter), params.fmt, params.precision);
break;
case detail::decimal_type::decimal64:
r = to_chars(buffer + byte_count, buffer + buf_size - byte_count - 1, static_cast<decimal64>(value), params.fmt, params.precision);
r = to_chars(buffer, buffer + buf_size - byte_count, static_cast<decimal64>(*value_iter), params.fmt, params.precision);
break;
default:
r = to_chars(buffer + byte_count, buffer + buf_size - byte_count - 1, static_cast<decimal128>(value), params.fmt, params.precision);
r = to_chars(buffer, buffer + buf_size - byte_count, static_cast<decimal128>(*value_iter), params.fmt, params.precision);
break;
}

if (!r)
{
// LCOV_EXCL_START
errno = static_cast<int>(r.ec);
return -1;
// LCOV_EXCL_STOP
}

*r.ptr = '\0';
// Adjust the capitalization and locale
if (params.upper_case)
{
detail::make_uppercase(buffer, r.ptr);
}
convert_pointer_pair_to_local_locale(buffer, r.ptr);

detail::convert_string_to_local_locale(buffer);
buffer = r.ptr;

byte_count += r.ptr - buffer;
if (value_iter != values_list.end())
{
++value_iter;
}
}

return static_cast<int>(byte_count);
*buffer = '\0';
return static_cast<int>(buffer - buffer_begin);
}

} // namespace detail
Expand All @@ -212,8 +251,7 @@ inline auto snprintf(char* buffer, std::size_t buf_size, const char* format, T..
-> int requires detail::is_decimal_floating_point_v<std::common_type_t<T...>>
#endif
{
const auto params = detail::parse_format(format);
return detail::snprintf_impl(buffer, buf_size, format, params, values...);
return detail::snprintf_impl(buffer, buf_size, format, values...);
}

template <typename... T>
Expand All @@ -224,10 +262,70 @@ inline auto sprintf(char* buffer, const char* format, T... values) noexcept
-> int requires detail::is_decimal_floating_point_v<std::common_type_t<T...>>
#endif
{
const auto params = detail::parse_format(format);
return detail::snprintf_impl(buffer, sizeof(buffer), format, params, values...);
return detail::snprintf_impl(buffer, sizeof(buffer), format, values...);
}

template <typename... T>
inline auto fprintf(std::FILE* buffer, const char* format, T... values) noexcept
#ifndef BOOST_DECIMAL_HAS_CONCEPTS
-> std::enable_if_t<detail::is_decimal_floating_point_v<std::common_type_t<T...>>, int>
#else
-> int requires detail::is_decimal_floating_point_v<std::common_type_t<T...>>
#endif
{
if (format == nullptr)
{
return -1;
}

// Heuristics for how much extra space we need to write the values
using common_t = std::common_type_t<T...>;
const std::initializer_list<common_t> values_list {values...};
const auto value_space {detail::max_string_length_v<common_t> * values_list.size()};

const auto format_len{std::strlen(format)};
int bytes {};
char char_buffer[1024];

if (format_len + value_space <= 1024U)
{
bytes = detail::snprintf_impl(char_buffer, sizeof(char_buffer), format, values...);
if (bytes)
{
bytes += static_cast<int>(std::fwrite(char_buffer, sizeof(char), static_cast<std::size_t>(bytes), buffer));
}
}
else
{
// LCOV_EXCL_START
std::unique_ptr<char[]> longer_char_buffer(new(std::nothrow) char[format_len + value_space + 1]);
if (longer_char_buffer == nullptr)
{
errno = ENOMEM;
return -1;
}

bytes = detail::snprintf_impl(longer_char_buffer.get(), format_len, format, values...);
if (bytes)
{
bytes += static_cast<int>(std::fwrite(longer_char_buffer.get(), sizeof(char), static_cast<std::size_t>(bytes), buffer));
}
// LCOV_EXCL_STOP
}

return bytes;
}

template <typename... T>
inline auto printf(const char* format, T... values) noexcept
#ifndef BOOST_DECIMAL_HAS_CONCEPTS
-> std::enable_if_t<detail::is_decimal_floating_point_v<std::common_type_t<T...>>, int>
#else
-> int requires detail::is_decimal_floating_point_v<std::common_type_t<T...>>
#endif
{
return fprintf(stdout, format, values...);
}

} // namespace decimal
} // namespace boost
Expand Down
17 changes: 17 additions & 0 deletions include/boost/decimal/detail/locale_conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ inline void convert_string_to_local_locale(char* buffer) noexcept
}
}

inline void convert_pointer_pair_to_local_locale(char* first, const char* last) noexcept
{
const auto locale_decimal_point = *std::localeconv()->decimal_point;
if (locale_decimal_point != '.')
{
while (first != last)
{
if (*first == '.')
{
*first = locale_decimal_point;
}

++first;
}
}
}

} //namespace detail
} //namespace decimal
} //namespace boost
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ run test_exp.cpp ;
run test_expm1.cpp ;
run test_fenv.cpp ;
run test_float_conversion.cpp ;
run-fail test_fprintf.cpp ;
run test_frexp_ldexp.cpp ;
run test_from_chars.cpp ;
run test_git_issue_266.cpp ;
Expand Down
2 changes: 2 additions & 0 deletions test/test_big_uints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ int main()
# pragma clang diagnostic ignored "-Wconversion"
# pragma clang diagnostic ignored "-Wsign-conversion"
# pragma clang diagnostic ignored "-Wfloat-equal"
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wold-style-cast"
# pragma GCC diagnostic ignored "-Wundef"
# pragma GCC diagnostic ignored "-Wconversion"
# pragma GCC diagnostic ignored "-Wsign-conversion"
# pragma GCC diagnostic ignored "-Wfloat-equal"
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

#if defined(__GNUC__) && !defined(__clang__)
Expand Down
25 changes: 25 additions & 0 deletions test/test_fprintf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

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

int main()
{
constexpr boost::decimal::decimal32 d {1, 2};

boost::decimal::printf("%Hg", d);
std::cout << std::endl;

boost::decimal::printf("%Hg %Hg", d, d);
std::cout << std::endl;

boost::decimal::printf("%Hg %Hg %Hg", d, d, d);
std::cout << std::endl;

boost::decimal::printf("Value 1: %Hg, Value 2: %Hg, Value 3: %Hg", d, d, d);
std::cout << std::endl;

return 1;
}
27 changes: 27 additions & 0 deletions test/test_snprintf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,31 @@ void test_uppercase(T value, const char* format_sprintf, chars_format fmt = char
BOOST_TEST_EQ(num_bytes, r.ptr - charconv_buffer);
}

void test_locales()
{
const char buffer[] = "1,1897e+02";

try
{
#ifdef BOOST_MSVC
std::locale::global(std::locale("German"));
#else
std::locale::global(std::locale("de_DE.UTF-8"));
#endif
}
// LCOV_EXCL_START
catch (...)
{
std::cerr << "Locale not installed. Skipping test." << std::endl;
return;
}
// LCOV_EXCL_STOP

char printf_buffer[256];
snprintf(printf_buffer, sizeof(printf_buffer), "%.4De", decimal64{11897, -2});
BOOST_TEST_CSTR_EQ(printf_buffer, buffer);
}

template <typename T>
void test_bootstrap()
{
Expand Down Expand Up @@ -149,5 +174,7 @@ int main()
test_bootstrap<decimal64>();
test_bootstrap<decimal128>();

test_locales();

return boost::report_errors();
}

0 comments on commit e109d0e

Please sign in to comment.