From e5b4f3f0c310a8396bc1fd5556c3662be012ca09 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Apr 2023 14:50:22 +0200 Subject: [PATCH] Improve trunc handling --- .../boost/math/special_functions/round.hpp | 1 + .../boost/math/special_functions/trunc.hpp | 36 +++++++- test/git_issue_430.cpp | 92 +++++++++++++++++++ 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/include/boost/math/special_functions/round.hpp b/include/boost/math/special_functions/round.hpp index 454ee1a55..a1287d941 100644 --- a/include/boost/math/special_functions/round.hpp +++ b/include/boost/math/special_functions/round.hpp @@ -1,4 +1,5 @@ // Copyright John Maddock 2007. +// Copyright Matt Borland 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) diff --git a/include/boost/math/special_functions/trunc.hpp b/include/boost/math/special_functions/trunc.hpp index d128d2c48..93a3f3056 100644 --- a/include/boost/math/special_functions/trunc.hpp +++ b/include/boost/math/special_functions/trunc.hpp @@ -1,4 +1,5 @@ // Copyright John Maddock 2007. +// Copyright Matt Borland 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) @@ -16,6 +17,13 @@ #include #include +#if __cplusplus >= 201703L || _MSVC_LANG >= 201703L +#include +# if !defined(BOOST_MATH_NO_CONSTEXPR_DETECTION) && !defined(TEST_GROUP_7) // Concept tests throw off constexpr ldexp +# define BOOST_MATH_HAS_CONSTEXPR_LDEXP +# endif +#endif + namespace boost{ namespace math{ namespace detail{ template @@ -67,7 +75,14 @@ inline int itrunc(const T& v, const Policy& pol) BOOST_MATH_STD_USING using result_type = tools::promote_args_t; result_type r = boost::math::trunc(v, pol); - if(r > static_cast((std::numeric_limits::max)()) || r < static_cast((std::numeric_limits::min)())) + + #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 = ldexp(static_cast(1), std::numeric_limits::digits); + #endif + + if(r >= max_val || r < -max_val) { return static_cast(policies::raise_rounding_error("boost::math::itrunc<%1%>(%1%)", nullptr, static_cast(v), 0, pol)); } @@ -85,7 +100,14 @@ inline long ltrunc(const T& v, const Policy& pol) BOOST_MATH_STD_USING using result_type = tools::promote_args_t; result_type r = boost::math::trunc(v, pol); - if(r > static_cast((std::numeric_limits::max)()) || r < static_cast((std::numeric_limits::min)())) + + #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 = ldexp(static_cast(1), std::numeric_limits::digits); + #endif + + if(r >= max_val || r < -max_val) { return static_cast(policies::raise_rounding_error("boost::math::ltrunc<%1%>(%1%)", nullptr, static_cast(v), 0L, pol)); } @@ -103,8 +125,14 @@ inline long long lltrunc(const T& v, const Policy& pol) BOOST_MATH_STD_USING using result_type = tools::promote_args_t; result_type r = boost::math::trunc(v, pol); - if(r > static_cast((std::numeric_limits::max)()) || - r < static_cast((std::numeric_limits::min)())) + + #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 = ldexp(static_cast(1), std::numeric_limits::digits); + #endif + + if(r >= max_val || r < -max_val) { return static_cast(policies::raise_rounding_error("boost::math::lltrunc<%1%>(%1%)", nullptr, v, static_cast(0), pol)); } diff --git a/test/git_issue_430.cpp b/test/git_issue_430.cpp index 236168179..053c9c553 100644 --- a/test/git_issue_430.cpp +++ b/test/git_issue_430.cpp @@ -7,6 +7,7 @@ // See: https://godbolt.org/z/Ev4ManrsW #include +#include #include #define BOOST_TEST_MAIN #include @@ -94,12 +95,103 @@ void test_iround_near_boundary() } } +template +void test_lltrunc_near_boundary() +{ + using std::ldexp; + Real boundary = ldexp(static_cast(1), std::numeric_limits::digits); + + Real value; + int i; + + for (value = boundary, i = 0; i < 100; value = boost::math::float_next(value), ++i) + { + BOOST_CHECK_THROW(boost::math::lltrunc(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::lltrunc(value)), boost::math::lltrunc(value)); + } + for (value = boost::math::float_prior(-boundary), i = 0; i < 100; value = boost::math::float_prior(value), ++i) + { + BOOST_CHECK_THROW(boost::math::lltrunc(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::lltrunc(value)), boost::math::lltrunc(value)); + } +} + +template +void test_ltrunc_near_boundary() +{ + using std::ldexp; + Real boundary = ldexp(static_cast(1), std::numeric_limits::digits); + + Real value; + int i; + + for (value = boundary, i = 0; i < 100; value = boost::math::float_next(value), ++i) + { + BOOST_CHECK_THROW(boost::math::ltrunc(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::ltrunc(value)), boost::math::ltrunc(value)); + } + for (value = boost::math::float_prior(-boundary), i = 0; i < 100; value = boost::math::float_prior(value), ++i) + { + BOOST_CHECK_THROW(boost::math::ltrunc(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::ltrunc(value)), boost::math::ltrunc(value)); + } +} + +template +void test_itrunc_near_boundary() +{ + using std::ldexp; + Real boundary = ldexp(static_cast(1), std::numeric_limits::digits); + + Real value; + int i; + + for (value = boundary, i = 0; i < 100; value = boost::math::float_next(value), ++i) + { + BOOST_CHECK_THROW(boost::math::itrunc(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::itrunc(value)), boost::math::itrunc(value)); + } + for (value = boost::math::float_prior(-boundary), i = 0; i < 100; value = boost::math::float_prior(value), ++i) + { + BOOST_CHECK_THROW(boost::math::itrunc(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::itrunc(value)), boost::math::itrunc(value)); + } +} + + BOOST_AUTO_TEST_CASE( test_main ) { + // Round test_llround_near_boundary(); test_llround_near_boundary(); test_lround_near_boundary(); test_iround_near_boundary(); + + // Trunc + test_lltrunc_near_boundary(); + test_lltrunc_near_boundary(); + + test_ltrunc_near_boundary(); + + test_itrunc_near_boundary(); }