diff --git a/include/boost/decimal/detail/cmath/cbrt.hpp b/include/boost/decimal/detail/cmath/cbrt.hpp index 884ac8ea3..d1af63159 100644 --- a/include/boost/decimal/detail/cmath/cbrt.hpp +++ b/include/boost/decimal/detail/cmath/cbrt.hpp @@ -1,5 +1,5 @@ -// Copyright 2023 - 2024 Matt Borland -// Copyright 2023 - 2024 Christopher Kormanyos +// Copyright 2023 - 2025 Matt Borland +// Copyright 2023 - 2025 Christopher Kormanyos // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt @@ -55,20 +55,16 @@ constexpr auto cbrt_impl(const T x) noexcept const auto gn { frexp10(x, &exp10val) }; - const auto - zeros_removal - { - remove_trailing_zeros(gn) - }; + int removed_zeros { }; - const bool is_pure { static_cast(zeros_removal.trimmed_number) == 1U }; + const bool is_pure { detail::patch::is_pure_p10(gn, &removed_zeros) }; if(is_pure) { // Here, a pure power-of-10 argument gets a straightforward result. // For argument 10^n where n is a multiple of 3, the result is exact. - const int p10 { exp10val + static_cast(zeros_removal.number_of_removed_zeros) }; + const int p10 { exp10val + removed_zeros }; if (p10 == 0) { @@ -131,14 +127,14 @@ constexpr auto cbrt_impl(const T x) noexcept (five + gx * (seventy + gx * 56)) / (numbers::cbrt2_v * (fourteen + gx * (seventy + gx * 20))); - // Perform 2, 3 or 4 Newton-Raphson iterations depending on precision. + // Perform 3, 4 or 5 Newton-Raphson iterations depending on precision. // Note from above, we start with slightly more than 2 decimal digits // of accuracy. constexpr int iter_loops { - std::numeric_limits::digits10 < 10 ? 2 - : std::numeric_limits::digits10 < 20 ? 3 : 4 + std::numeric_limits::digits10 < 10 ? 3 + : std::numeric_limits::digits10 < 20 ? 4 : 5 }; for (int idx = 0; idx < iter_loops; ++idx) diff --git a/include/boost/decimal/detail/cmath/log10.hpp b/include/boost/decimal/detail/cmath/log10.hpp index 3b3b32bec..bab6585f8 100644 --- a/include/boost/decimal/detail/cmath/log10.hpp +++ b/include/boost/decimal/detail/cmath/log10.hpp @@ -1,5 +1,5 @@ -// Copyright 2023 Matt Borland -// Copyright 2023 Christopher Kormanyos +// Copyright 2023 - 2025 Matt Borland +// Copyright 2023 - 2025 Christopher Kormanyos // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt @@ -57,18 +57,14 @@ constexpr auto log10_impl(const T x) noexcept const auto gn { frexp10(x, &exp10val) }; - const auto - zeros_removal - { - remove_trailing_zeros(gn) - }; + int removed_zeros { }; - const bool is_pure { static_cast(zeros_removal.trimmed_number) == 1U }; + const bool is_pure { detail::patch::is_pure_p10(gn, &removed_zeros) }; if(is_pure) { // Here, a pure power-of-10 argument gets a pure integral result. - const int p10 { exp10val + static_cast(zeros_removal.number_of_removed_zeros) }; + const int p10 { exp10val + removed_zeros }; result = T { p10 }; } diff --git a/include/boost/decimal/detail/cmath/pow.hpp b/include/boost/decimal/detail/cmath/pow.hpp index 13fd14874..f1936bf43 100644 --- a/include/boost/decimal/detail/cmath/pow.hpp +++ b/include/boost/decimal/detail/cmath/pow.hpp @@ -1,5 +1,5 @@ -// Copyright 2023 Matt Borland -// Copyright 2023 Christopher Kormanyos +// Copyright 2023 - 2025 Matt Borland +// Copyright 2023 - 2025 Christopher Kormanyos // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt @@ -101,18 +101,14 @@ constexpr auto pow(const T b, const IntegralType p) noexcept const auto bn { frexp10(b, &exp10val) }; - const auto - zeros_removal - { - detail::remove_trailing_zeros(bn) - }; + int removed_zeros { }; - const bool is_pure { static_cast(zeros_removal.trimmed_number) == 1 }; + const bool is_pure { detail::patch::is_pure_p10(bn, &removed_zeros) }; if(is_pure) { // Here, a pure power-of-10 argument (b) gets a pure integral result. - const int log10_val { exp10val + static_cast(zeros_removal.number_of_removed_zeros) }; + const int log10_val { exp10val + removed_zeros }; result = T { 1, static_cast(log10_val * static_cast(p)) }; } diff --git a/include/boost/decimal/detail/cmath/sqrt.hpp b/include/boost/decimal/detail/cmath/sqrt.hpp index e68dcb36d..faa651146 100644 --- a/include/boost/decimal/detail/cmath/sqrt.hpp +++ b/include/boost/decimal/detail/cmath/sqrt.hpp @@ -1,5 +1,5 @@ -// Copyright 2023 - 2024 Matt Borland -// Copyright 2023 - 2024 Christopher Kormanyos +// Copyright 2023 - 2025 Matt Borland +// Copyright 2023 - 2025 Christopher Kormanyos // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt @@ -55,13 +55,9 @@ constexpr auto sqrt_impl(const T x) noexcept const auto gn { frexp10(x, &exp10val) }; - const auto - zeros_removal - { - remove_trailing_zeros(gn) - }; + int removed_zeros { }; - const bool is_pure { static_cast(zeros_removal.trimmed_number) == 1U }; + const bool is_pure { detail::patch::is_pure_p10(gn, &removed_zeros) }; constexpr T one { 1 }; @@ -70,7 +66,7 @@ constexpr auto sqrt_impl(const T x) noexcept // Here, a pure power-of-10 argument gets a straightforward result. // For argument 10^n where n is even, the result is exact. - const int p10 { exp10val + static_cast(zeros_removal.number_of_removed_zeros) }; + const int p10 { exp10val + removed_zeros }; if (p10 == 0) { @@ -123,7 +119,7 @@ constexpr auto sqrt_impl(const T x) noexcept (one + gx * ((one + gx) * 20)) / (numbers::sqrt2_v * ((gx * 4) * (five + gx) + five)); - // Perform 2, 3 or 4 Newton-Raphson iterations depending on precision. + // Perform 3, 4 or 5 Newton-Raphson iterations depending on precision. // Note from above, we start with slightly more than 2 decimal digits // of accuracy. diff --git a/include/boost/decimal/detail/remove_trailing_zeros.hpp b/include/boost/decimal/detail/remove_trailing_zeros.hpp index 0cf1e93c1..b3958905e 100644 --- a/include/boost/decimal/detail/remove_trailing_zeros.hpp +++ b/include/boost/decimal/detail/remove_trailing_zeros.hpp @@ -175,6 +175,57 @@ constexpr auto remove_trailing_zeros(boost::int128::detail::builtin_u128 n) noex #endif +namespace patch { + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // Unreachable code +#endif + +template +constexpr auto is_pure_p10(const typename T::significand_type& gn, int* p_removed_zeros) noexcept -> bool +{ + const auto + zeros_removal + { + remove_trailing_zeros(gn) + }; + + bool is_pure { static_cast(zeros_removal.trimmed_number) == 1 }; + + BOOST_DECIMAL_IF_CONSTEXPR(std::numeric_limits::digits > 64) + { + // Catch a rare overflow case for 128-bit decimal types when counting trailing zeros and + // patch it here. See also Git issue: https://github.com/cppalliance/decimal/issues/1110 + + if(is_pure) + { + // Scale the argument to the interval 1 <= x < 10. + // Scaling is needed to check floor-argument equality. + const T gx { gn, -std::numeric_limits::digits10 + 1 }; + + if(floor(gx) != gx) + { + is_pure = false; + } + } + } + + if(p_removed_zeros != nullptr) + { + *p_removed_zeros = static_cast(zeros_removal.number_of_removed_zeros); + } + + return is_pure; +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace patch + + } // namespace detail } // namespace decimal } // namespace boost diff --git a/test/Jamfile b/test/Jamfile index dc1b781dc..85110ff04 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -69,6 +69,7 @@ run github_issue_1057.cpp ; compile-fail github_issue_1087.cpp ; run github_issue_1091.cpp ; run github_issue_1094.cpp ; +run github_issue_1110.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; run random_decimal32_comp.cpp ; diff --git a/test/github_issue_1110.cpp b/test/github_issue_1110.cpp new file mode 100644 index 000000000..7430b0bb3 --- /dev/null +++ b/test/github_issue_1110.cpp @@ -0,0 +1,104 @@ +// Copyright 2025 Christopher Kormanyos +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// See: https://github.com/cppalliance/decimal/issues/1110 + +#include +#include +#include + +#include + +#include +#include +#include + +namespace local { + +auto test_near1() -> void; +auto test_large() -> void; + +auto test_near1() -> void +{ + const boost::decimal::decimal128_t one { 1 }; + const boost::decimal::decimal128_t del { 1, -32 }; + const boost::decimal::decimal128_t sum { one + del }; + + const boost::decimal::decimal128_t sqr { sqrt(sum) }; + + { + std::stringstream strm { }; + + strm << std::setprecision(std::numeric_limits::digits10) << sqr; + + BOOST_TEST(strm.str() == "1.000000000000000000000000000000005"); + } + + const boost::decimal::decimal128_t cbr { cbrt(sum) }; + + { + std::stringstream strm { }; + + strm << std::setprecision(std::numeric_limits::digits10)<< cbr; + + BOOST_TEST(strm.str() == "1.000000000000000000000000000000003"); + } + + const boost::decimal::decimal128_t lgt { log10(sum) }; + + { + std::stringstream strm { }; + + strm << std::setprecision(std::numeric_limits::digits10)<< lgt; + + BOOST_TEST(strm.str() == "4.4e-33"); + } +} + +auto test_large() -> void +{ + const boost::decimal::decimal128_t one { 1, 100 }; + const boost::decimal::decimal128_t del { 1, 68 }; + const boost::decimal::decimal128_t sum { one + del }; + + const boost::decimal::decimal128_t sqr { sqrt(sum) }; + + { + std::stringstream strm { }; + + strm << std::setprecision(std::numeric_limits::digits10) << sqr; + + BOOST_TEST(strm.str() == "1.000000000000000000000000000000005e+50"); + } + + const boost::decimal::decimal128_t cbr { cbrt(sum) }; + + { + std::stringstream strm { }; + + strm << std::setprecision(std::numeric_limits::digits10)<< cbr; + + BOOST_TEST(strm.str() == "2154434690031883721759293566519356"); + } + + const boost::decimal::decimal128_t lgt { log10(sum) }; + + { + std::stringstream strm { }; + + strm << std::setprecision(std::numeric_limits::digits10)<< lgt; + + BOOST_TEST(strm.str() == "100"); + } +} + +} // namespace local + +auto main() -> int +{ + local::test_near1(); + local::test_large(); + + return boost::report_errors(); +}