From b80d6489c56664d0ddc1f2138decf54ae62ade4a Mon Sep 17 00:00:00 2001 From: Sergiu Deitsch Date: Sat, 20 Dec 2025 00:37:29 +0100 Subject: [PATCH] Karma: fix broken `real_generator` output for `long double` Eliminate Boost 1.90.0 regression introduced in e6fbdf615b43fa7a03db4877079e572481a55f35. --- .../home/karma/numeric/real_policies.hpp | 36 +++++++++++-------- test/karma/Jamfile | 1 + .../regression_real_policy_precision.cpp | 35 ++++++++++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 test/karma/regression_real_policy_precision.cpp diff --git a/include/boost/spirit/home/karma/numeric/real_policies.hpp b/include/boost/spirit/home/karma/numeric/real_policies.hpp index 974e60fb2..10b2fa7cd 100644 --- a/include/boost/spirit/home/karma/numeric/real_policies.hpp +++ b/include/boost/spirit/home/karma/numeric/real_policies.hpp @@ -257,20 +257,28 @@ namespace boost { namespace spirit { namespace karma // generate(sink, right_align(precision, '0')[ulong], n); // but it's spelled out to avoid inter-modular dependencies. - unsigned int digits=1; //should be number of digits n(truncating any fraction) - if(!boost::spirit::traits::test_zero(n)) { - static constexpr uint64_t limit = UINT64_MAX / 10; - const T num = floor(n); - for (uint64_t x = 10u, i = 1u;; x *= 10, i++) { - if (num < x) { - digits=i;break; - } - if (x > limit) { - digits= i + 1;break; - } - } - } - + // should be number of digits n(truncating any fraction) + typename remove_const::type digits = 1; + + if (!boost::spirit::traits::test_zero(n)) { + BOOST_CONSTEXPR_OR_CONST uint64_t limit = + std::numeric_limits::max() / 10; + // Cannot cast T to uint64_t since the former might be a mocked + // type that does not support type casting + const T num = floor(n); + + if (num > limit) { + // uint64_t cannot represent the fractional part of an 80 + // bit floating point type at full precision. Fallback to + // convential computation. + digits = ceil(log10(n + T(1.))); + } else { + for (uint64_t x = 10; num >= x; x *= 10) { + ++digits; + } + } + } + bool r = true; for (/**/; r && digits < precision_; digits = digits + 1) r = char_inserter<>::call(sink, '0'); diff --git a/test/karma/Jamfile b/test/karma/Jamfile index f7b5f0cd8..04a4f869c 100644 --- a/test/karma/Jamfile +++ b/test/karma/Jamfile @@ -145,6 +145,7 @@ run regression_iterator.cpp ; run regression_optional_double.cpp ; run regression_real_0.cpp ; run regression_real_policy_sign.cpp ; +run regression_real_policy_precision.cpp ; run regression_real_scientific.cpp ; run regression_semantic_action_attribute.cpp ; run regression_unicode_char.cpp : : : off ; diff --git a/test/karma/regression_real_policy_precision.cpp b/test/karma/regression_real_policy_precision.cpp new file mode 100644 index 000000000..bf362dbb2 --- /dev/null +++ b/test/karma/regression_real_policy_precision.cpp @@ -0,0 +1,35 @@ +// Distributed under 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) + +#include +#include + +#include "real.hpp" + +template +struct max_precision_policy : boost::spirit::karma::real_policies { + static BOOST_CONSTEXPR unsigned precision(T /*unused*/) BOOST_NOEXCEPT { + return std::numeric_limits::max_digits10; + } +}; + +int main() { + using namespace boost::spirit; + + boost::spirit::karma::real_generator> + real; + + BOOST_TEST(test("1.004999999999999999996", real, 1.005l)); + BOOST_TEST(test("1.049999999999999999956", real, 1.05l)); + BOOST_TEST(test("1.549999999999999999968", real, 1.55l)); + + // Construct the fractional part as the limit of uint64_t plus one as + // 1844674407370955265 = (2^64-1)/10)+1 + // to ensure the formatting does not break above this internal limit. + BOOST_TEST(test("1.001844674407370955251", real, 1.001844674407370955251l)); + BOOST_TEST(test("1.01844674407370955262", real, 1.01844674407370955262l)); + BOOST_TEST(test("1.184467440737095526528", real, 1.184467440737095526528l)); + + return boost::report_errors(); +}