diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cf1e04..055f6b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,6 @@ target_link_libraries(boost_lexical_cast Boost::container Boost::core Boost::integer - Boost::numeric_conversion Boost::throw_exception Boost::type_traits ) diff --git a/doc/lexical_cast.qbk b/doc/lexical_cast.qbk index 010e505..77711d3 100644 --- a/doc/lexical_cast.qbk +++ b/doc/lexical_cast.qbk @@ -242,6 +242,7 @@ limitation of compiler options that you use. * less template instantiations and simpler maintainance; * support for `volatile` input types was dropped, following the C++ Standard Library trend. * Optimized conversions from std::basic_string_view and boost::basic_string_view + * Dropped dependency on Boost.NumericConversion and Boost.MPL * [*boost 1.84.0 :] diff --git a/include/boost/lexical_cast/detail/converter_numeric.hpp b/include/boost/lexical_cast/detail/converter_numeric.hpp index 95b0334..5357058 100644 --- a/include/boost/lexical_cast/detail/converter_numeric.hpp +++ b/include/boost/lexical_cast/detail/converter_numeric.hpp @@ -23,101 +23,104 @@ # pragma once #endif +#include +#include #include #include #include #include #include -#include #include -#include #include -#include - -#include namespace boost { namespace detail { -template -struct detect_precision_loss -{ - typedef Source source_type; - typedef boost::numeric::Trunc Rounder; - typedef typename conditional< - boost::is_arithmetic::value, Source, Source const& - >::type argument_type ; - - static inline source_type nearbyint(argument_type s, bool& is_ok) noexcept { - const source_type near_int = Rounder::nearbyint(s); - if (near_int && is_ok) { - const source_type orig_div_round = s / near_int; - const source_type eps = std::numeric_limits::epsilon(); - - is_ok = !((orig_div_round > 1 ? orig_div_round - 1 : 1 - orig_div_round) > eps); - } +template +bool ios_numeric_comparer_float(Source x, Source y) noexcept { + return x == y + || (boost::core::isnan(x) && boost::core::isnan(y)) + || (x < (std::numeric_limits::min)()) + ; +} + +template +constexpr bool is_out_of_range_for(T value) noexcept { + return value > static_cast((std::numeric_limits::max)()) + || value < static_cast((std::numeric_limits::min)()); +} - return s; + +// integral -> integral +template +typename boost::enable_if_c< + !boost::is_floating_point::value && !boost::is_floating_point::value, bool +>::type noexcept_numeric_convert(Source arg, Target& result) noexcept { + const Target target_tmp = static_cast(arg); + const Source arg_restored = static_cast(target_tmp); + if (arg == arg_restored) { + result = target_tmp; + return true; } + return false; +} - typedef typename Rounder::round_style round_style; -}; +// integral -> floating point +template +typename boost::enable_if_c< + !boost::is_floating_point::value && boost::is_floating_point::value, bool +>::type noexcept_numeric_convert(Source arg, Target& result) noexcept { + const Target target_tmp = static_cast(arg); + result = target_tmp; + return true; +} -template -struct fake_precision_loss: public Base -{ - typedef Source source_type ; - typedef typename conditional< - boost::is_arithmetic::value, Source, Source const& - >::type argument_type ; - static inline source_type nearbyint(argument_type s, bool& /*is_ok*/) noexcept { - return s; +// floating point -> floating point +template +typename boost::enable_if_c< + boost::is_floating_point::value && boost::is_floating_point::value, bool +>::type noexcept_numeric_convert(Source arg, Target& result) noexcept { + const Target target_tmp = static_cast(arg); + const Source arg_restored = static_cast(target_tmp); + if (detail::ios_numeric_comparer_float(arg, arg_restored)) { + result = target_tmp; + return true; } -}; -struct nothrow_overflow_handler -{ - inline bool operator() ( boost::numeric::range_check_result r ) const noexcept { - return (r == boost::numeric::cInRange); - } -}; + return false; +} +// floating point -> integral template -inline bool noexcept_numeric_convert(const Source& arg, Target& result) noexcept { - typedef boost::numeric::converter< - Target, - Source, - boost::numeric::conversion_traits, - nothrow_overflow_handler, - detect_precision_loss - > converter_orig_t; - - typedef typename boost::conditional< - boost::is_base_of< detect_precision_loss, converter_orig_t >::value, - converter_orig_t, - fake_precision_loss - >::type converter_t; - - bool res = nothrow_overflow_handler()(converter_t::out_of_range(arg)); - if (res) { - result = converter_t::low_level_convert(converter_t::nearbyint(arg, res)); +typename boost::enable_if_c< + boost::is_floating_point::value && !boost::is_floating_point::value, bool +>::type noexcept_numeric_convert(Source arg, Target& result) noexcept { + if (detail::is_out_of_range_for(arg)) { + return false; + } + + const Target target_tmp = static_cast(arg); + const Source arg_restored = static_cast(target_tmp); + if (detail::ios_numeric_comparer_float(arg, arg_restored)) { + result = target_tmp; + return true; } - return res; + return false; } -template struct lexical_cast_dynamic_num_not_ignoring_minus { - static inline bool try_convert(const Source &arg, Target& result) noexcept { - return noexcept_numeric_convert(arg, result); + template + static inline bool try_convert(Source arg, Target& result) noexcept { + return boost::detail::noexcept_numeric_convert(arg, result); } }; -template struct lexical_cast_dynamic_num_ignoring_minus { - static inline bool try_convert(const Source &arg, Target& result) noexcept { + template + static inline bool try_convert(Source arg, Target& result) noexcept { typedef typename boost::conditional< boost::is_float::value, boost::type_identity, @@ -126,17 +129,17 @@ struct lexical_cast_dynamic_num_ignoring_minus typedef typename usource_lazy_t::type usource_t; if (arg < 0) { - const bool res = noexcept_numeric_convert(0u - arg, result); - result = static_cast(0u - result); + const bool res = boost::detail::noexcept_numeric_convert(0u - arg, result); + result = static_cast(0u) - static_cast(result); return res; } else { - return noexcept_numeric_convert(arg, result); + return boost::detail::noexcept_numeric_convert(arg, result); } } }; /* - * lexical_cast_dynamic_num follows the rules: + * dynamic_num_converter_impl follows the rules: * 1) If Source can be converted to Target without precision loss and * without overflows, then assign Source to Target and return * @@ -156,16 +159,14 @@ struct lexical_cast_dynamic_num_ignoring_minus template struct dynamic_num_converter_impl { - typedef typename boost::remove_volatile::type source_type; - - static inline bool try_convert(source_type arg, Target& result) noexcept { + static inline bool try_convert(Source arg, Target& result) noexcept { typedef typename boost::conditional< boost::is_unsigned::value && - (boost::is_signed::value || boost::is_float::value) && - !(boost::is_same::value) && + (boost::is_signed::value || boost::is_float::value) && + !(boost::is_same::value) && !(boost::is_same::value), - lexical_cast_dynamic_num_ignoring_minus, - lexical_cast_dynamic_num_not_ignoring_minus + lexical_cast_dynamic_num_ignoring_minus, + lexical_cast_dynamic_num_not_ignoring_minus >::type caster_type; return caster_type::try_convert(arg, result); diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 5835782..08a67b5 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -45,6 +45,13 @@ test-suite conversion [ run implicit_convert.cpp ] [ run wchars_test.cpp ] [ run float_types_test.cpp ] + + # Make sure that LexicalCast works the same way as some of the C++ Standard Libraries + [ run float_types_test.cpp : : : BOOST_LEXICAL_CAST_DETAIL_TEST_ON_OLD + msvc:no # could have outdated behavior in some border cases + : float_types_non_opt + ] + [ run inf_nan_test.cpp ] [ run containers_test.cpp : : : gcc:-Wno-long-long clang:-Wno-long-long ] [ run empty_input_test.cpp ] @@ -55,7 +62,6 @@ test-suite conversion [ run no_exceptions_test.cpp : : : off _HAS_EXCEPTIONS=0 # MSVC stdlib _STLP_NO_EXCEPTIONS # STLPort - -/boost/test//boost_unit_test_framework # uses lightweight_test ] [ run iterator_range_test.cpp ] [ run string_view_test.cpp ] diff --git a/test/float_types_test.cpp b/test/float_types_test.cpp index 79d790d..bcbd2c4 100644 --- a/test/float_types_test.cpp +++ b/test/float_types_test.cpp @@ -8,14 +8,23 @@ // Software License, Version 1.0. (See accompanying file // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt). +#ifndef BOOST_LEXICAL_CAST_DETAIL_TEST_ON_OLD #include +#else +// Make sure that tests work the same way on non-optimized version +#include "lexical_cast_old.hpp" +#endif #include #include +#include +#include #ifndef BOOST_TEST_CLOSE_FRACTION // Naiive, but works for most tests in this file -#define BOOST_TEST_CLOSE_FRACTION(x, y, eps) BOOST_TEST(x - y + eps <= eps * 2) +#define BOOST_TEST_CLOSE_FRACTION(x, y, eps) \ + BOOST_TEST(x - y + eps <= eps * 2); \ + BOOST_TEST(y - x + eps <= eps * 2); #endif #if (defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__NetBSD__) \ @@ -303,6 +312,13 @@ void test_float_typess_for_overflows() && lexical_cast( (std::numeric_limits::min)() ) <= (std::numeric_limits::min)() + std::numeric_limits::epsilon() ); + + BOOST_TEST( + (std::numeric_limits::min)() / 2 - std::numeric_limits::epsilon() + <= lexical_cast( (std::numeric_limits::min)() / 2 ) + && lexical_cast( (std::numeric_limits::min)() / 2 ) + <= (std::numeric_limits::min)() / 2 + std::numeric_limits::epsilon() + ); } if ( sizeof(test_t) < sizeof(long double) ) @@ -314,6 +330,13 @@ void test_float_typess_for_overflows() && lexical_cast( (std::numeric_limits::min)() ) <= (std::numeric_limits::min)() + std::numeric_limits::epsilon() ); + + BOOST_TEST( + (std::numeric_limits::min)() / 2 - std::numeric_limits::epsilon() + <= lexical_cast( (std::numeric_limits::min)() / 2 ) + && lexical_cast( (std::numeric_limits::min)() / 2 ) + <= (std::numeric_limits::min)() / 2 + std::numeric_limits::epsilon() + ); } } @@ -488,6 +511,7 @@ void test_conversion_from_to_float() } + void test_conversion_from_to_float() { test_conversion_from_to_float(); @@ -505,11 +529,66 @@ void test_conversion_from_to_long_double() BOOST_TEST(true); } +template +void test_conversion_integral_float() +{ + BOOST_TEST_CLOSE_FRACTION(lexical_cast(static_cast(1)), static_cast(1), std::numeric_limits::epsilon()); + BOOST_TEST_CLOSE_FRACTION(lexical_cast(static_cast(1.1234)), static_cast(1.1234), std::numeric_limits::epsilon()); + BOOST_TEST_CLOSE_FRACTION(lexical_cast(static_cast(-1.1234)), static_cast(-1.1234), std::numeric_limits::epsilon()); + + BOOST_TEST_CLOSE_FRACTION(lexical_cast(static_cast(0)), static_cast(0), std::numeric_limits::epsilon()); + BOOST_TEST_CLOSE_FRACTION(lexical_cast(static_cast(1)), static_cast(1), std::numeric_limits::epsilon()); + +#ifndef __CYGWIN__ + BOOST_TEST_CLOSE_FRACTION(lexical_cast((std::numeric_limits::max)()), static_cast((std::numeric_limits::max)()), std::numeric_limits::epsilon()); + BOOST_TEST_CLOSE_FRACTION(lexical_cast((std::numeric_limits::min)()), static_cast((std::numeric_limits::min)()), std::numeric_limits::epsilon()); +#endif + + BOOST_TEST_EQ(lexical_cast(static_cast(0.0)), 0); + BOOST_TEST_EQ(lexical_cast(static_cast(1.0)), 1); + BOOST_TEST_EQ(lexical_cast(static_cast(8.0)), 8); + BOOST_TEST_EQ(lexical_cast(static_cast(16.0)), 16); + + if (boost::is_signed::value) { + BOOST_TEST_EQ(lexical_cast(static_cast(-1.0)), -1); + BOOST_TEST_EQ(lexical_cast(static_cast(-8.0)), -8); + BOOST_TEST_EQ(lexical_cast(static_cast(-16.0)), -16); + } else { + BOOST_TEST_EQ(lexical_cast(static_cast(-1.0)), (std::numeric_limits::max)()); + BOOST_TEST_EQ(lexical_cast(static_cast(-8.0)), (std::numeric_limits::max)() - 7); + BOOST_TEST_EQ(lexical_cast(static_cast(-16.0)), (std::numeric_limits::max)() - 15); + } + + BOOST_TEST_THROWS(lexical_cast(static_cast(0.5)), bad_lexical_cast); + BOOST_TEST_THROWS(lexical_cast(static_cast(-0.5)), bad_lexical_cast); + BOOST_TEST_THROWS(lexical_cast(static_cast(1.5)), bad_lexical_cast); + BOOST_TEST_THROWS(lexical_cast(static_cast(-1.5)), bad_lexical_cast); + + BOOST_TEST_THROWS(lexical_cast((std::numeric_limits::min)()), bad_lexical_cast); + BOOST_TEST_THROWS(lexical_cast((std::numeric_limits::max)()), bad_lexical_cast); + BOOST_TEST_THROWS(lexical_cast((std::numeric_limits::epsilon)()), bad_lexical_cast); + BOOST_TEST_THROWS(lexical_cast((std::numeric_limits::lowest)()), bad_lexical_cast); +} + + int main() { test_conversion_from_to_float(); test_conversion_from_to_double(); test_conversion_from_to_long_double(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + test_conversion_integral_float(); + return boost::report_errors(); } diff --git a/test/inf_nan_test.cpp b/test/inf_nan_test.cpp index bd8e3c3..1698a84 100644 --- a/test/inf_nan_test.cpp +++ b/test/inf_nan_test.cpp @@ -167,11 +167,36 @@ void test_inf_nan_templated() void test_inf_nan_float() { test_inf_nan_templated(); + + BOOST_TEST(is_pos_nan(lexical_cast(std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_neg_nan(lexical_cast(-std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_pos_inf(lexical_cast(std::numeric_limits::infinity()))); + BOOST_TEST(is_neg_inf(lexical_cast(-std::numeric_limits::infinity()))); + + BOOST_TEST(is_pos_nan(lexical_cast(std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_neg_nan(lexical_cast(-std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_pos_inf(lexical_cast(std::numeric_limits::infinity()))); + BOOST_TEST(is_neg_inf(lexical_cast(-std::numeric_limits::infinity()))); } void test_inf_nan_double() { test_inf_nan_templated(); + + BOOST_TEST(is_pos_nan(lexical_cast(std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_neg_nan(lexical_cast(-std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_pos_inf(lexical_cast(std::numeric_limits::infinity()))); + BOOST_TEST(is_neg_inf(lexical_cast(-std::numeric_limits::infinity()))); + + BOOST_TEST(is_pos_nan(lexical_cast(std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_neg_nan(lexical_cast(-std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_pos_inf(lexical_cast(std::numeric_limits::infinity()))); + BOOST_TEST(is_neg_inf(lexical_cast(-std::numeric_limits::infinity()))); + + BOOST_TEST(is_pos_nan(lexical_cast(std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_neg_nan(lexical_cast(-std::numeric_limits::quiet_NaN()))); + BOOST_TEST(is_pos_inf(lexical_cast(std::numeric_limits::infinity()))); + BOOST_TEST(is_neg_inf(lexical_cast(-std::numeric_limits::infinity()))); } void test_inf_nan_long_double() diff --git a/test/integral_types_test.cpp b/test/integral_types_test.cpp index 148ceec..f85bc9f 100644 --- a/test/integral_types_test.cpp +++ b/test/integral_types_test.cpp @@ -599,6 +599,33 @@ void test_integral_conversions_on_min_max() } +void test_negative_integral() { + // From https://github.com/boostorg/lexical_cast/issues/45 + BOOST_TEST_EQ(boost::lexical_cast("-6575543"), -6575543); + + BOOST_TEST_EQ(boost::lexical_cast(-6575543), -6575543); + + BOOST_TEST_EQ(boost::lexical_cast("+6575543"), +6575543); + BOOST_TEST_EQ(boost::lexical_cast(6575543), 6575543); + + if (sizeof(short) == 2 && CHAR_BIT == 8) { + BOOST_TEST_EQ(boost::lexical_cast("-32768"), -32768); + BOOST_TEST_EQ(boost::lexical_cast(-32768), -32768); + BOOST_TEST_EQ(boost::lexical_cast("-32768"), 32768); + BOOST_TEST_EQ(boost::lexical_cast(-32768), 32768); + + BOOST_TEST_EQ(boost::lexical_cast(65535), 65535); + + BOOST_TEST_THROWS(boost::lexical_cast(-65536), bad_lexical_cast); + BOOST_TEST_EQ(boost::lexical_cast(-65535), 1); + BOOST_TEST_EQ(boost::lexical_cast(-65534), 2); + + BOOST_TEST_THROWS(boost::lexical_cast(65535), bad_lexical_cast); + BOOST_TEST_THROWS(boost::lexical_cast(-65536), bad_lexical_cast); + BOOST_TEST_THROWS(boost::lexical_cast(-65535), bad_lexical_cast); + } +} + int main() { test_conversion_from_to_short(); @@ -619,6 +646,8 @@ int main() #endif test_integral_conversions_on_min_max(); + test_negative_integral(); + return boost::report_errors(); } diff --git a/include/boost/lexical_cast/lexical_cast_old.hpp b/test/lexical_cast_old.hpp similarity index 99% rename from include/boost/lexical_cast/lexical_cast_old.hpp rename to test/lexical_cast_old.hpp index cca0e38..6b1e150 100644 --- a/include/boost/lexical_cast/lexical_cast_old.hpp +++ b/test/lexical_cast_old.hpp @@ -159,7 +159,7 @@ namespace boost { typedef std::char_traits traits; detail::lexical_stream interpreter; - Target result; + Target result{}; if(!(interpreter << arg && interpreter >> result)) boost::conversion::detail::throw_bad_cast();