From faad5eaa2dae41a19ff248f2d6b6d65f3a220fe0 Mon Sep 17 00:00:00 2001 From: ckormanyos Date: Sun, 12 Oct 2025 17:48:19 +0200 Subject: [PATCH 1/5] Attempt rudimentary repair issue 1110 --- include/boost/decimal/detail/cmath/cbrt.hpp | 20 +++++------ include/boost/decimal/detail/cmath/log10.hpp | 14 +++----- include/boost/decimal/detail/cmath/pow.hpp | 14 +++----- include/boost/decimal/detail/cmath/sqrt.hpp | 16 ++++----- .../decimal/detail/remove_trailing_zeros.hpp | 34 +++++++++++++++++++ 5 files changed, 58 insertions(+), 40 deletions(-) diff --git a/include/boost/decimal/detail/cmath/cbrt.hpp b/include/boost/decimal/detail/cmath/cbrt.hpp index 884ac8ea3..c4d2df38c 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::is_pure_p10(x, 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..ef8555410 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::is_pure_p10(x, 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..a294347ba 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::is_pure_p10(b, 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..f616f7d34 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::is_pure_p10(x, 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..274629179 100644 --- a/include/boost/decimal/detail/remove_trailing_zeros.hpp +++ b/include/boost/decimal/detail/remove_trailing_zeros.hpp @@ -175,6 +175,40 @@ constexpr auto remove_trailing_zeros(boost::int128::detail::builtin_u128 n) noex #endif +template +constexpr auto is_pure_p10(const T& x, 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 }; + + // Catch a rare overflow case for decimal128 types and patch it here. + // See also Git issue: https://github.com/cppalliance/decimal/issues/1110 + if(is_pure) + { + BOOST_DECIMAL_IF_CONSTEXPR(std::numeric_limits::digits > 64) + { + constexpr T ten { 1, 1 }; + + if((x < ten) && (floor(x) != x)) + { + is_pure = false; + } + } + } + + if(p_removed_zeros != nullptr) + { + *p_removed_zeros = static_cast(zeros_removal.number_of_removed_zeros); + } + + return is_pure; +} + } // namespace detail } // namespace decimal } // namespace boost From aca022244b2906de76cbd2cfeab3a88c88081ece Mon Sep 17 00:00:00 2001 From: ckormanyos Date: Sun, 12 Oct 2025 21:36:34 +0200 Subject: [PATCH 2/5] Add tests and disable MSVC warning --- .../decimal/detail/remove_trailing_zeros.hpp | 18 +++-- test/Jamfile | 1 + test/github_issue_1110.cpp | 67 +++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 test/github_issue_1110.cpp diff --git a/include/boost/decimal/detail/remove_trailing_zeros.hpp b/include/boost/decimal/detail/remove_trailing_zeros.hpp index 274629179..6aa696fbc 100644 --- a/include/boost/decimal/detail/remove_trailing_zeros.hpp +++ b/include/boost/decimal/detail/remove_trailing_zeros.hpp @@ -175,6 +175,11 @@ constexpr auto remove_trailing_zeros(boost::int128::detail::builtin_u128 n) noex #endif +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // Unreachable code +#endif + template constexpr auto is_pure_p10(const T& x, const typename T::significand_type& gn, int* p_removed_zeros) noexcept -> bool { @@ -186,11 +191,12 @@ constexpr auto is_pure_p10(const T& x, const typename T::significand_type& gn, i bool is_pure { static_cast(zeros_removal.trimmed_number) == 1 }; - // Catch a rare overflow case for decimal128 types and patch it here. - // See also Git issue: https://github.com/cppalliance/decimal/issues/1110 - if(is_pure) + BOOST_DECIMAL_IF_CONSTEXPR(std::numeric_limits::digits > 64) { - 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) { constexpr T ten { 1, 1 }; @@ -209,6 +215,10 @@ constexpr auto is_pure_p10(const T& x, const typename T::significand_type& gn, i return is_pure; } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + } // 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..e984e791f --- /dev/null +++ b/test/github_issue_1110.cpp @@ -0,0 +1,67 @@ +// 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 + +using namespace boost::decimal; + +namespace local { + +auto test() -> void; + +auto test() -> 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"); + } +} + +} // namespace local + +auto main() -> int +{ + local::test(); + + return boost::report_errors(); +} From 1277d0339fc48d6fc6f0a8c15e3dbee04ef06e86 Mon Sep 17 00:00:00 2001 From: ckormanyos Date: Sun, 12 Oct 2025 21:40:14 +0200 Subject: [PATCH 3/5] Remove an unused line --- test/github_issue_1110.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/github_issue_1110.cpp b/test/github_issue_1110.cpp index e984e791f..d483cb0a0 100644 --- a/test/github_issue_1110.cpp +++ b/test/github_issue_1110.cpp @@ -14,8 +14,6 @@ #include #include -using namespace boost::decimal; - namespace local { auto test() -> void; From bb4f129b44cd83c0f6ab13b1d966b314fad97179 Mon Sep 17 00:00:00 2001 From: ckormanyos Date: Mon, 13 Oct 2025 09:48:45 +0200 Subject: [PATCH 4/5] Handle wide range of argument orders in patch --- .../decimal/detail/remove_trailing_zeros.hpp | 6 ++- test/github_issue_1110.cpp | 45 +++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/include/boost/decimal/detail/remove_trailing_zeros.hpp b/include/boost/decimal/detail/remove_trailing_zeros.hpp index 6aa696fbc..8a09772d0 100644 --- a/include/boost/decimal/detail/remove_trailing_zeros.hpp +++ b/include/boost/decimal/detail/remove_trailing_zeros.hpp @@ -198,9 +198,11 @@ constexpr auto is_pure_p10(const T& x, const typename T::significand_type& gn, i if(is_pure) { - constexpr T ten { 1, 1 }; + // Scale the argument to the interval 1 <= x < 10. + // Scaling is needed to check floor-argument equality. + T gx { gn, -std::numeric_limits::digits10 + 1 }; - if((x < ten) && (floor(x) != x)) + if(floor(gx) != gx) { is_pure = false; } diff --git a/test/github_issue_1110.cpp b/test/github_issue_1110.cpp index d483cb0a0..7430b0bb3 100644 --- a/test/github_issue_1110.cpp +++ b/test/github_issue_1110.cpp @@ -16,9 +16,10 @@ namespace local { -auto test() -> void; +auto test_near1() -> void; +auto test_large() -> void; -auto test() -> void +auto test_near1() -> void { const boost::decimal::decimal128_t one { 1 }; const boost::decimal::decimal128_t del { 1, -32 }; @@ -55,11 +56,49 @@ auto test() -> void } } +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(); + local::test_near1(); + local::test_large(); return boost::report_errors(); } From c84d7d15a58a97ac15996b51f97ff46eed647dd1 Mon Sep 17 00:00:00 2001 From: ckormanyos Date: Mon, 13 Oct 2025 10:17:53 +0200 Subject: [PATCH 5/5] Add patch namespace and remove unused param --- include/boost/decimal/detail/cmath/cbrt.hpp | 2 +- include/boost/decimal/detail/cmath/log10.hpp | 2 +- include/boost/decimal/detail/cmath/pow.hpp | 2 +- include/boost/decimal/detail/cmath/sqrt.hpp | 2 +- include/boost/decimal/detail/remove_trailing_zeros.hpp | 9 +++++++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/boost/decimal/detail/cmath/cbrt.hpp b/include/boost/decimal/detail/cmath/cbrt.hpp index c4d2df38c..d1af63159 100644 --- a/include/boost/decimal/detail/cmath/cbrt.hpp +++ b/include/boost/decimal/detail/cmath/cbrt.hpp @@ -57,7 +57,7 @@ constexpr auto cbrt_impl(const T x) noexcept int removed_zeros { }; - const bool is_pure { detail::is_pure_p10(x, gn, &removed_zeros) }; + const bool is_pure { detail::patch::is_pure_p10(gn, &removed_zeros) }; if(is_pure) { diff --git a/include/boost/decimal/detail/cmath/log10.hpp b/include/boost/decimal/detail/cmath/log10.hpp index ef8555410..bab6585f8 100644 --- a/include/boost/decimal/detail/cmath/log10.hpp +++ b/include/boost/decimal/detail/cmath/log10.hpp @@ -59,7 +59,7 @@ constexpr auto log10_impl(const T x) noexcept int removed_zeros { }; - const bool is_pure { detail::is_pure_p10(x, gn, &removed_zeros) }; + const bool is_pure { detail::patch::is_pure_p10(gn, &removed_zeros) }; if(is_pure) { diff --git a/include/boost/decimal/detail/cmath/pow.hpp b/include/boost/decimal/detail/cmath/pow.hpp index a294347ba..f1936bf43 100644 --- a/include/boost/decimal/detail/cmath/pow.hpp +++ b/include/boost/decimal/detail/cmath/pow.hpp @@ -103,7 +103,7 @@ constexpr auto pow(const T b, const IntegralType p) noexcept int removed_zeros { }; - const bool is_pure { detail::is_pure_p10(b, bn, &removed_zeros) }; + const bool is_pure { detail::patch::is_pure_p10(bn, &removed_zeros) }; if(is_pure) { diff --git a/include/boost/decimal/detail/cmath/sqrt.hpp b/include/boost/decimal/detail/cmath/sqrt.hpp index f616f7d34..faa651146 100644 --- a/include/boost/decimal/detail/cmath/sqrt.hpp +++ b/include/boost/decimal/detail/cmath/sqrt.hpp @@ -57,7 +57,7 @@ constexpr auto sqrt_impl(const T x) noexcept int removed_zeros { }; - const bool is_pure { detail::is_pure_p10(x, gn, &removed_zeros) }; + const bool is_pure { detail::patch::is_pure_p10(gn, &removed_zeros) }; constexpr T one { 1 }; diff --git a/include/boost/decimal/detail/remove_trailing_zeros.hpp b/include/boost/decimal/detail/remove_trailing_zeros.hpp index 8a09772d0..b3958905e 100644 --- a/include/boost/decimal/detail/remove_trailing_zeros.hpp +++ b/include/boost/decimal/detail/remove_trailing_zeros.hpp @@ -175,13 +175,15 @@ 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 T& x, const typename T::significand_type& gn, int* p_removed_zeros) noexcept -> bool +constexpr auto is_pure_p10(const typename T::significand_type& gn, int* p_removed_zeros) noexcept -> bool { const auto zeros_removal @@ -200,7 +202,7 @@ constexpr auto is_pure_p10(const T& x, const typename T::significand_type& gn, i { // Scale the argument to the interval 1 <= x < 10. // Scaling is needed to check floor-argument equality. - T gx { gn, -std::numeric_limits::digits10 + 1 }; + const T gx { gn, -std::numeric_limits::digits10 + 1 }; if(floor(gx) != gx) { @@ -221,6 +223,9 @@ constexpr auto is_pure_p10(const T& x, const typename T::significand_type& gn, i # pragma warning(pop) #endif +} // namespace patch + + } // namespace detail } // namespace decimal } // namespace boost