diff --git a/doc/decimal/charconv.adoc b/doc/decimal/charconv.adoc index df146c81..10c846ce 100644 --- a/doc/decimal/charconv.adoc +++ b/doc/decimal/charconv.adoc @@ -86,12 +86,21 @@ namespace boost { namespace decimal { template -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 +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++] @@ -108,6 +117,16 @@ BOOST_DECIMAL_CONSTEXPR to_chars_result to_chars(char* first, char* last, Decima template 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 +BOOST_DECIMAL_CONSTEXPR std::to_chars_result to_chars(char* first, char* last, DecimalType value, std::chars_format fmt) noexcept; + +template +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 ---- @@ -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] diff --git a/doc/decimal/config.adoc b/doc/decimal/config.adoc index 71988278..f578f489 100644 --- a/doc/decimal/config.adoc +++ b/doc/decimal/config.adoc @@ -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 `` 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 diff --git a/include/boost/decimal/charconv.hpp b/include/boost/decimal/charconv.hpp index 1ad74ea8..21a7a2ce 100644 --- a/include/boost/decimal/charconv.hpp +++ b/include/boost/decimal/charconv.hpp @@ -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 +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 // --------------------------------------------------------------------------------------------------------------------- @@ -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 +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 +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 struct limits { diff --git a/include/boost/decimal/detail/config.hpp b/include/boost/decimal/detail/config.hpp index 7507557a..21798fe9 100644 --- a/include/boost/decimal/detail/config.hpp +++ b/include/boost/decimal/detail/config.hpp @@ -312,4 +312,17 @@ typedef unsigned __int128 uint128_t; # define BOOST_DECIMAL_FAST_MATH #endif +#if __cplusplus >= 201703L +# if __has_include() + // 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 +# endif +# define BOOST_DECIMAL_HAS_STD_CHARCONV +# endif +# endif +#endif + #endif // BOOST_DECIMAL_DETAIL_CONFIG_HPP diff --git a/test/test_from_chars.cpp b/test/test_from_chars.cpp index bc612897..4964a5de 100644 --- a/test/test_from_chars.cpp +++ b/test/test_from_chars.cpp @@ -202,6 +202,122 @@ void test_hex_values() BOOST_TEST_EQ(v3, res_3); } +#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV + +template +void test_from_chars_scientific_std() +{ + std::uniform_real_distribution dist(1e-10F, 1e10F); + + constexpr auto max_iter {std::is_same::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(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 +void test_from_chars_fixed_std() +{ + std::uniform_real_distribution dist(1e-10F, 1e10F); + + constexpr auto max_iter {std::is_same::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(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 +void test_from_chars_general_std() +{ + std::uniform_real_distribution dist(1e-10F, 1e10F); + + constexpr auto max_iter {std::is_same::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(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(); @@ -219,6 +335,23 @@ int main() test_from_chars_general(); test_from_chars_general(); + #ifdef BOOST_DECIMAL_HAS_STD_CHARCONV + test_from_chars_scientific_std(); + test_from_chars_scientific_std(); + test_from_chars_scientific_std(); + test_from_chars_scientific_std(); + + test_from_chars_fixed_std(); + test_from_chars_fixed_std(); + test_from_chars_fixed_std(); + test_from_chars_fixed_std(); + + test_from_chars_general_std(); + test_from_chars_general_std(); + test_from_chars_general_std(); + test_from_chars_general_std(); + #endif + test_non_finite_values(); test_non_finite_values(); test_non_finite_values(); diff --git a/test/test_to_chars.cpp b/test/test_to_chars.cpp index a6b7479c..c9b2ee4e 100644 --- a/test/test_to_chars.cpp +++ b/test/test_to_chars.cpp @@ -235,6 +235,178 @@ void test_hex_format() } } +#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV + +template +void test_scientific_format_std() +{ + constexpr double max_value = 1e10; + std::uniform_real_distribution dist(-max_value, max_value); + + for (std::size_t i {}; i < N; ++i) + { + char buffer[256] {}; + + const auto val {dist(rng)}; + const T dec_val {val}; + + std::to_chars_result to_r = to_chars(buffer, buffer + sizeof(buffer), dec_val, std::chars_format::scientific); + BOOST_TEST(to_r.ec == std::errc()); + + T ret_val; + std::from_chars_result from_r = from_chars(buffer, buffer + std::strlen(buffer), ret_val, std::chars_format::scientific); + if (!BOOST_TEST(from_r.ec == std::errc())) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << "Value: " << dec_val + << "\nBuffer: " << buffer + << "\nError: " << static_cast(from_r.ec) << std::endl; + + continue; + // LCOV_EXCL_STOP + } + + if (!BOOST_TEST_EQ(dec_val, ret_val)) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << " Value: " << dec_val + << "\n Buffer: " << buffer + << "\nRet val: " << ret_val << std::endl; + // LCOV_EXCL_STOP + } + } +} + +template +void test_fixed_format_std() +{ + constexpr double max_value = 1e10; + std::uniform_real_distribution dist(-max_value, max_value); + + for (std::size_t i {}; i < N; ++i) + { + char buffer[256] {}; + + const auto val {dist(rng)}; + const T dec_val {val}; + + std::to_chars_result to_r = to_chars(buffer, buffer + sizeof(buffer), dec_val, std::chars_format::fixed); + BOOST_TEST(to_r.ec == std::errc()); + + T ret_val; + std::from_chars_result from_r = from_chars(buffer, buffer + std::strlen(buffer), ret_val, std::chars_format::fixed); + if (!BOOST_TEST(from_r.ec == std::errc())) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << "Value: " << dec_val + << "\nBuffer: " << buffer + << "\nError: " << static_cast(from_r.ec) << std::endl; + + continue; + // LCOV_EXCL_STOP + } + + if (!BOOST_TEST_EQ(dec_val, ret_val)) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << " Value: " << dec_val + << "\n Buffer: " << buffer + << "\nRet val: " << ret_val << std::endl; + // LCOV_EXCL_STOP + } + } +} + +template +void test_hex_format_std() +{ + constexpr double max_value = 1e10; + std::uniform_real_distribution dist(-max_value, max_value); + + for (std::size_t i {}; i < N; ++i) + { + char buffer[256] {}; + + const auto val {dist(rng)}; + const T dec_val {val}; + + std::to_chars_result to_r = to_chars(buffer, buffer + sizeof(buffer), dec_val, std::chars_format::hex); + BOOST_TEST(to_r.ec == std::errc()); + + T ret_val; + std::from_chars_result from_r = from_chars(buffer, buffer + std::strlen(buffer), ret_val, std::chars_format::hex); + if (!BOOST_TEST(from_r.ec == std::errc())) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << "Value: " << dec_val + << "\nBuffer: " << buffer + << "\nError: " << static_cast(from_r.ec) << std::endl; + + continue; + // LCOV_EXCL_STOP + } + + if (!BOOST_TEST_EQ(dec_val, ret_val)) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << " Value: " << dec_val + << "\n Buffer: " << buffer + << "\nRet val: " << ret_val << std::endl; + // LCOV_EXCL_STOP + } + } +} + +template +void test_general_format_std() +{ + constexpr double max_value = 1e10; + std::uniform_real_distribution dist(-max_value, max_value); + + for (std::size_t i {}; i < N; ++i) + { + char buffer[256] {}; + + const auto val {dist(rng)}; + const T dec_val {val}; + + std::to_chars_result to_r = to_chars(buffer, buffer + sizeof(buffer), dec_val, std::chars_format::general); + BOOST_TEST(to_r.ec == std::errc()); + + T ret_val; + std::from_chars_result from_r = from_chars(buffer, buffer + std::strlen(buffer), ret_val, std::chars_format::general); + if (!BOOST_TEST(from_r.ec == std::errc())) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << "Value: " << dec_val + << "\nBuffer: " << buffer + << "\nError: " << static_cast(from_r.ec) << std::endl; + + continue; + // LCOV_EXCL_STOP + } + + if (!BOOST_TEST_EQ(dec_val, ret_val)) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::digits10) + << " Value: " << dec_val + << "\n Buffer: " << buffer + << "\nRet val: " << ret_val << std::endl; + // LCOV_EXCL_STOP + } + } +} + +#endif + template void test_value(T val, chars_format fmt, int precision, const char* result) { @@ -667,6 +839,20 @@ int main() test_error_value("e1000a00000000000000000000p06", chars_format::hex, precision); } + #ifdef BOOST_DECIMAL_HAS_STD_CHARCONV + test_scientific_format_std(); + test_scientific_format_std(); + + test_fixed_format_std(); + test_fixed_format_std(); + + test_hex_format_std(); + test_hex_format_std(); + + test_general_format_std(); + test_general_format_std(); + #endif + return boost::report_errors(); }