Skip to content

Commit

Permalink
Merge pull request #752 from cppalliance/charconv_conversions
Browse files Browse the repository at this point in the history
Enable interoperability with `std::` types from `<charconv>`
  • Loading branch information
mborland authored Oct 4, 2024
2 parents c21c6b7 + db52157 commit 1e1fdfe
Show file tree
Hide file tree
Showing 6 changed files with 453 additions and 1 deletion.
23 changes: 22 additions & 1 deletion doc/decimal/charconv.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,21 @@ namespace boost {
namespace decimal {
template <typename DecimalType>
constexpr from_chars_result from_chars(const char* first, const char* last, DecimalType& value, chars_format fmt = chars_format::general)
constexpr boost::decimal::from_chars_result from_chars(const char* first, const char* last, DecimalType& value, boost::decimal::chars_format fmt = boost::decimal::chars_format::general) noexcept
#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV
template <typename DecimalType>
constexpr std::from_chars_result from_chars(const char* first, const char* last, DecimalType& value, std::chars_format fmt) noexcept
#endif // BOOST_DECIMAL_HAS_STD_CHARCONV
} //namespace decimal
} //namespace boost
----

IMPORTANT: If `std::chars_format` is used the function will return a `std::from_chars_result` and if `boost::decimal::chars_format` is used *OR* no format is specified then a `boost::decimal::from_chars_result` will be returned.

[#to_chars]
== to_chars
[source, c++]
Expand All @@ -108,6 +117,16 @@ BOOST_DECIMAL_CONSTEXPR to_chars_result to_chars(char* first, char* last, Decima
template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR to_chars_result to_chars(char* first, char* last, DecimalType value, chars_format fmt, int precision) noexcept;
#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV
template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR std::to_chars_result to_chars(char* first, char* last, DecimalType value, std::chars_format fmt) noexcept;
template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR std::to_chars_result to_chars(char* first, char* last, DecimalType value, std::chars_format fmt, int precision) noexcept;
#endif // BOOST_DECIMAL_HAS_STD_CHARCONV
} //namespace decimal
} //namespace boost
----
Expand All @@ -119,6 +138,8 @@ NOTE: `BOOST_DECIMAL_CONSTEXPR` is defined if:
- Compiler has: `__builtin_is_constant_evaluated()`
- C++20 support with: `std::is_constant_evaluated()`

IMPORTANT: Same as `from_chars`, `boost::decimal::to_chars` will return a `std::to_chars_result` if `std::chars_format` is used to specify the format; otherwise it returns a `boost::decimal::to_chars_result`.

The library offers an additional feature for sizing buffers without specified precision and in general format

[#charconv_limits]
Expand Down
4 changes: 4 additions & 0 deletions doc/decimal/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ This flag increases the performance of the basis operations (e.g. add, sub, mul,
* __GNUC__ >= 9
* Compiler has: __builtin_is_constant_evaluated()
* C++20 support with: std::is_constant_evaluated()

- `BOOST_DECIMAL_HAS_STD_CHARCONV`: This macro is defined if header `<charconv>` exists and the language standard used is >= C++17
* We only need the structs and enums out of the header so we are not concerned with being overly restrictive about the feature test macros.
** Known compilers that support this lighter requirement are: GCC >= 10, Clang >= 13, and MSVC >= 14.2
95 changes: 95 additions & 0 deletions include/boost/decimal/charconv.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,36 @@ BOOST_DECIMAL_EXPORT constexpr auto from_chars(const char* first, const char* la
return detail::from_chars_general_impl(first, last, value, fmt);
}

#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV
BOOST_DECIMAL_EXPORT template <typename DecimalType>
constexpr auto from_chars(const char* first, const char* last, DecimalType& value, std::chars_format fmt) noexcept
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::from_chars_result)
{
from_chars_result boost_r {};
switch (fmt)
{
case std::chars_format::scientific:
boost_r = from_chars(first, last, value, chars_format::scientific);
break;
case std::chars_format::fixed:
boost_r = from_chars(first, last, value, chars_format::fixed);
break;
case std::chars_format::hex:
boost_r = from_chars(first, last, value, chars_format::hex);
break;
case std::chars_format::general:
boost_r = from_chars(first, last, value, chars_format::general);
break;
// LCOV_EXCL_START
default:
BOOST_DECIMAL_UNREACHABLE;
// LCOV_EXCL_STOP
}

return std::from_chars_result {boost_r.ptr, boost_r.ec};
}
#endif

// ---------------------------------------------------------------------------------------------------------------------
// to_chars and implementation
// ---------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -911,6 +941,71 @@ BOOST_DECIMAL_EXPORT BOOST_DECIMAL_CONSTEXPR auto to_chars(char* first, char* la
return detail::to_chars_impl(first, last, value, fmt, precision);
}

#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV

BOOST_DECIMAL_EXPORT template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR auto to_chars(char* first, char* last, DecimalType value, std::chars_format fmt)
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::to_chars_result)
{
to_chars_result boost_r {};
switch (fmt)
{
case std::chars_format::scientific:
boost_r = detail::to_chars_impl(first, last, value, chars_format::scientific);
break;
case std::chars_format::fixed:
boost_r = detail::to_chars_impl(first, last, value, chars_format::fixed);
break;
case std::chars_format::hex:
boost_r = detail::to_chars_impl(first, last, value, chars_format::hex);
break;
case std::chars_format::general:
boost_r = detail::to_chars_impl(first, last, value, chars_format::general);
break;
// LCOV_EXCL_START
default:
BOOST_DECIMAL_UNREACHABLE;
// LCOV_EXCL_STOP
}

return std::to_chars_result {boost_r.ptr, boost_r.ec};
}

BOOST_DECIMAL_EXPORT template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR auto to_chars(char* first, char* last, DecimalType value, std::chars_format fmt, int precision)
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::to_chars_result)
{
if (precision < 0)
{
precision = 6;
}

to_chars_result boost_r {};
switch (fmt)
{
case std::chars_format::scientific:
boost_r = detail::to_chars_impl(first, last, value, chars_format::scientific, precision);
break;
case std::chars_format::fixed:
boost_r = detail::to_chars_impl(first, last, value, chars_format::fixed, precision);
break;
case std::chars_format::hex:
boost_r = detail::to_chars_impl(first, last, value, chars_format::hex, precision);
break;
case std::chars_format::general:
boost_r = detail::to_chars_impl(first, last, value, chars_format::general, precision);
break;
// LCOV_EXCL_START
default:
BOOST_DECIMAL_UNREACHABLE;
// LCOV_EXCL_STOP
}

return std::to_chars_result {boost_r.ptr, boost_r.ec};
}

#endif // BOOST_DECIMAL_HAS_STD_CHARCONV

template <typename T>
struct limits
{
Expand Down
13 changes: 13 additions & 0 deletions include/boost/decimal/detail/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,17 @@ typedef unsigned __int128 uint128_t;
# define BOOST_DECIMAL_FAST_MATH
#endif

#if __cplusplus >= 201703L
# if __has_include(<charconv>)
// We don't need all of charconv, just: std::to_chars_result, std::from_chars_result, and std::chars_format
// These compilers and versions give us what we need
# if (defined(__clang_major__) && __clang_major__ >= 13) || (defined(__GNUC__) && __GNUC__ >= 10) || defined(_MSC_VER)
# ifndef BOOST_DECIMAL_BUILD_MODULE
# include <charconv>
# endif
# define BOOST_DECIMAL_HAS_STD_CHARCONV
# endif
# endif
#endif

#endif // BOOST_DECIMAL_DETAIL_CONFIG_HPP
133 changes: 133 additions & 0 deletions test/test_from_chars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,122 @@ void test_hex_values()
BOOST_TEST_EQ(v3, res_3);
}

#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV

template <typename T>
void test_from_chars_scientific_std()
{
std::uniform_real_distribution<float> dist(1e-10F, 1e10F);

constexpr auto max_iter {std::is_same<T, decimal128>::value ? N / 4 : N};

for (std::size_t i {}; i < max_iter; ++i)
{
char buffer[256] {};
const auto val {dist(rng)};
auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val, boost::charconv::chars_format::scientific);

if (!r)
{
continue; // LCOV_EXCL_LINE
}

*r.ptr = '\0';

T return_value;
const std::from_chars_result r_dec = from_chars(buffer, buffer + std::strlen(buffer), return_value, std::chars_format::scientific);
const auto ret_value_float = static_cast<float>(return_value);
const auto float_distance = std::abs(boost::math::float_distance(ret_value_float, val));

if (!(BOOST_TEST(float_distance <= 10) && BOOST_TEST(r_dec.ec == std::errc())))
{
// LCOV_EXCL_START
std::cerr << " Value: " << val
<< "\n Buffer: " << buffer
<< "\n Ret Val: " << return_value
<< "\nFloat dist: " << float_distance << std::endl;
// LCOV_EXCL_STOP
}
}
}

template <typename T>
void test_from_chars_fixed_std()
{
std::uniform_real_distribution<float> dist(1e-10F, 1e10F);

constexpr auto max_iter {std::is_same<T, decimal128>::value ? N / 4 : N};

for (std::size_t i {}; i < max_iter; ++i)
{
char buffer[256] {};
const auto val {dist(rng)};
auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val, boost::charconv::chars_format::fixed);

if (!r)
{
continue; // LCOV_EXCL_LINE
}

*r.ptr = '\0';

T return_value;
const std::from_chars_result r_dec = from_chars(buffer, buffer + std::strlen(buffer), return_value, std::chars_format::fixed);

const auto ret_value_float = static_cast<float>(return_value);
const auto float_distance = std::abs(boost::math::float_distance(ret_value_float, val));

if (!(BOOST_TEST(float_distance <= 10) && BOOST_TEST(r_dec.ec == std::errc())))
{
// LCOV_EXCL_START
std::cerr << " Value: " << val
<< "\n Buffer: " << buffer
<< "\n Ret Val: " << return_value
<< "\nFloat dist: " << float_distance << std::endl;
// LCOV_EXCL_STOP
}
}
}

template <typename T>
void test_from_chars_general_std()
{
std::uniform_real_distribution<float> dist(1e-10F, 1e10F);

constexpr auto max_iter {std::is_same<T, decimal128>::value ? N / 4 : N};

for (std::size_t i {}; i < max_iter; ++i)
{
char buffer[256] {};
const auto val {dist(rng)};
auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val, boost::charconv::chars_format::general);

if (!r)
{
continue; // LCOV_EXCL_LINE
}

*r.ptr = '\0';

T return_value;
const std::from_chars_result r_dec = from_chars(buffer, buffer + std::strlen(buffer), return_value, std::chars_format::general);
const auto ret_value_float = static_cast<float>(return_value);
const auto float_distance = std::abs(boost::math::float_distance(ret_value_float, val));

if (!(BOOST_TEST(float_distance <= 10) && BOOST_TEST(r_dec.ec == std::errc())))
{
// LCOV_EXCL_START
std::cerr << " Value: " << val
<< "\n Buffer: " << buffer
<< "\n Ret Val: " << return_value
<< "\nFloat dist: " << float_distance << std::endl;
// LCOV_EXCL_STOP
}
}
}

#endif

int main()
{
test_from_chars_scientific<decimal32>();
Expand All @@ -219,6 +335,23 @@ int main()
test_from_chars_general<decimal32_fast>();
test_from_chars_general<decimal64_fast>();

#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV
test_from_chars_scientific_std<decimal32>();
test_from_chars_scientific_std<decimal64>();
test_from_chars_scientific_std<decimal32_fast>();
test_from_chars_scientific_std<decimal64_fast>();

test_from_chars_fixed_std<decimal32>();
test_from_chars_fixed_std<decimal64>();
test_from_chars_fixed_std<decimal32_fast>();
test_from_chars_fixed_std<decimal64_fast>();

test_from_chars_general_std<decimal32>();
test_from_chars_general_std<decimal64>();
test_from_chars_general_std<decimal32_fast>();
test_from_chars_general_std<decimal64_fast>();
#endif

test_non_finite_values<decimal32>();
test_non_finite_values<decimal64>();
test_non_finite_values<decimal32_fast>();
Expand Down
Loading

0 comments on commit 1e1fdfe

Please sign in to comment.