diff --git a/include/boost/math/special_functions/round.hpp b/include/boost/math/special_functions/round.hpp index e6602908c6..7fe78d6c85 100644 --- a/include/boost/math/special_functions/round.hpp +++ b/include/boost/math/special_functions/round.hpp @@ -18,39 +18,17 @@ #include #include +#if __cplusplus >= 201703L || _MSVC_LANG >= 201703L +#include +# ifndef BOOST_MATH_NO_CONSTEXPR_DETECTION +# define BOOST_MATH_HAS_CONSTEXPR_LDEXP +# endif +#endif + namespace boost{ namespace math{ namespace detail{ -// https://stackoverflow.com/questions/8905246/how-to-check-if-float-can-be-exactly-represented-as-an-integer/17822304#17822304 -template -inline ResultType float_to_int(T x) -{ - BOOST_MATH_STD_USING - - constexpr int sign = std::is_signed::value ? -1 : 0; - - T y = floor(x); - if (y < static_cast(0.0L)) - { - return 0; - } - - if (y >= ldexp(static_cast(1.0L), (sizeof(ResultType) * CHAR_BIT) + sign) + sign) - { - return (std::numeric_limits::max)(); - } - - return static_cast(y); -} - -template -inline bool is_representable(T x) -{ - BOOST_MATH_STD_USING - return (floor(x) == x && x >= static_cast(0.0L) && x < ldexp(static_cast(1.0L), sizeof(TargetType) * CHAR_BIT)); -} - template inline tools::promote_args_t round(const T& v, const Policy& pol, const std::false_type&) { @@ -158,25 +136,21 @@ template inline long long llround(const T& v, const Policy& pol) { BOOST_MATH_STD_USING - using result_type = tools::promote_args_t; + using result_type = boost::math::tools::promote_args_t; - T r = boost::math::round(v, pol); - long long return_val = boost::math::detail::float_to_int(r); - bool representable = boost::math::detail::is_representable(r); + result_type r = boost::math::round(v, pol); - if ((return_val == (std::numeric_limits::max)() && !representable) || - r < static_cast((std::numeric_limits::min)()) || - r > static_cast((std::numeric_limits::max)())) - { - return static_cast(policies::raise_rounding_error("boost::math::llround<%1%>(%1%)", nullptr, v, static_cast(0), pol)); - } + #ifdef BOOST_MATH_HAS_CONSTEXPR_LDEXP + constexpr result_type max_val = boost::math::ccmath::ldexp(static_cast(1), std::numeric_limits::digits); + #else + static const result_type max_val = std::ldexp(static_cast(1), std::numeric_limits::digits); + #endif - if (r < 0) + if (r >= max_val || r < -max_val) { - return_val = static_cast(r); + return static_cast(boost::math::policies::raise_rounding_error("boost::math::llround<%1%>(%1%)", nullptr, v, static_cast(0), pol)); } - - return return_val; + return static_cast(r); } template inline long long llround(const T& v) diff --git a/test/git_issue_430.cpp b/test/git_issue_430.cpp index fd6bc89a93..907bc9ed5d 100644 --- a/test/git_issue_430.cpp +++ b/test/git_issue_430.cpp @@ -1,4 +1,5 @@ -// Copyright Matt Borland, 2023 +// Copyright Matt Borland 2023 +// Copyright John Maddock 2023 // Use, modification and distribution are subject to the // Boost Software License, Version 1.0. (See accompanying file // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -6,14 +7,40 @@ // See: https://godbolt.org/z/Ev4ManrsW #include +#define BOOST_TEST_MAIN +#include #include -#include "math_unit_test.hpp" -double x = 9223372036854775807.0; // can't be represented as double, will have a different value at runtime. -int main() +template +void test_llround_near_boundary() { - int64_t result = boost::math::llround(x); - CHECK_EQUAL(result, INT64_C(9223372036854775807)); + using std::ldexp; + Real boundary = ldexp(static_cast(1), std::numeric_limits::digits); - return boost::math::test::report_errors(); + Real value; + int i; + + for (value = boundary, i = 0; i < 100; value = boost::math::float_next(value), ++i) + { + BOOST_CHECK_THROW(boost::math::llround(value), boost::math::rounding_error); + } + for (value = boost::math::float_prior(boundary), i = 0; i < 1000; value = boost::math::float_prior(value), ++i) + { + BOOST_CHECK_EQUAL(static_cast(boost::math::llround(value)), boost::math::round(value)); + } + for (value = boost::math::float_prior(-boundary), i = 0; i < 100; value = boost::math::float_prior(value), ++i) + { + BOOST_CHECK_THROW(boost::math::llround(value), boost::math::rounding_error); + } + for (value = -boundary, i = 0; i < 1000; value = boost::math::float_next(value), ++i) + { + BOOST_CHECK_EQUAL(static_cast(boost::math::llround(value)), boost::math::round(value)); + } +} + +BOOST_AUTO_TEST_CASE( test_main ) +{ + test_llround_near_boundary(); + test_llround_near_boundary(); + test_llround_near_boundary(); }