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

Enable interoperability with std:: types from <charconv> #752

Merged
merged 8 commits into from
Oct 4, 2024
Merged
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
Loading