From 8504eca93160f30d1e4b1f0fa32a1394b00b8cfc Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 6 Aug 2026 14:02:57 -0400 Subject: [PATCH 1/4] Fix decimal quantum testing --- test/test_decimal_quantum.cpp | 102 +++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/test/test_decimal_quantum.cpp b/test/test_decimal_quantum.cpp index 3709eb2af..7f447c771 100644 --- a/test/test_decimal_quantum.cpp +++ b/test/test_decimal_quantum.cpp @@ -84,6 +84,8 @@ void test_same_quantum() } } +// quantexp returns the quantum exponent, not the biased encoded exponent field, +// so the IEEE types report back exactly the exponent they were constructed with. template void test_quantexp() { @@ -98,7 +100,7 @@ void test_quantexp() } else { - if (!BOOST_TEST_EQ(quantexp(val1), i + detail::bias_v)) + if (!BOOST_TEST_EQ(quantexp(val1), i)) // LCOV_EXCL_START { std::cerr << "Val: " << val1 @@ -109,6 +111,85 @@ void test_quantexp() } } +// The fast types renormalize the significand to full precision in their +// constructor, so {1, i} is stored as 10^(digits10 - 1) scaled by +// 10^(i - digits10 + 1), which is the quantum exponent quantexp must report. +template +void test_quantexp_fast() +{ + constexpr auto digits {std::numeric_limits::digits10}; + + for (auto i {std::numeric_limits::min_exponent10 + digits}; i < std::numeric_limits::max_exponent10 - digits; ++i) + { + const Dec val1 {1, i}; + + if (isinf(val1)) + { + continue; // LCOV_EXCL_LINE + } + + if (!BOOST_TEST_EQ(quantexp(val1), i - digits + 1)) + // LCOV_EXCL_START + { + std::cerr << "Val: " << val1 + << "\nExp 1: " << i << std::endl; + } + // LCOV_EXCL_STOP + + // frexp10 does not renormalize the fast types, so its exponent is an + // independent view of the same quantum exponent. + int frexp_exp {}; + static_cast(frexp10(val1, &frexp_exp)); + BOOST_TEST_EQ(quantexp(val1), frexp_exp); + } +} + +// Spot values from the bug report that the biased return value got wrong. +// The IEEE types preserve the quantum they were constructed with. +template +void test_quantexp_spot() +{ + BOOST_TEST_EQ(quantexp(Dec {3, 2}), 2); + BOOST_TEST_EQ(quantexp(Dec {3000000, -4}), -4); + BOOST_TEST_EQ(quantexp(Dec {5, 0}), 0); + + // The quantum exponent belongs to the cohort member, not to the value, so + // two equal values with different quanta report different exponents. + BOOST_TEST((Dec {3, 2} == Dec {3000000, -4})); + BOOST_TEST_NE(quantexp(Dec {3, 2}), quantexp(Dec {3000000, -4})); +} + +// samequantum compares two exponents, so it is immune to a uniform bias offset. +// Holding it against quantexp pins the two functions to the same definition +// for both the IEEE and the fast families. +template +void test_quantexp_samequantum_agreement() +{ + std::uniform_int_distribution sig(1, 9'999'999); + std::uniform_int_distribution exp(std::numeric_limits::min_exponent10 + 19, + std::numeric_limits::max_exponent10 - 19); + + constexpr auto max_iter {std::is_same::value ? N / 4 : N}; + for (std::size_t i {}; i < max_iter; ++i) + { + const Dec val1 {sig(rng), exp(rng)}; + const Dec val2 {sig(rng), exp(rng)}; + + if (!isfinite(val1) || !isfinite(val2)) + { + continue; // LCOV_EXCL_LINE + } + + if (!BOOST_TEST_EQ(samequantum(val1, val2), quantexp(val1) == quantexp(val2))) + // LCOV_EXCL_START + { + std::cerr << "Val 1: " << val1 + << "\nVal 2: " << val2 << std::endl; + } + // LCOV_EXCL_STOP + } +} + template void test_nonfinite_quantexp() { @@ -214,6 +295,8 @@ int main() test_same_quantum(); test_nonfinite_samequantum(); test_quantexp(); + test_quantexp_spot(); + test_quantexp_samequantum_agreement(); test_nonfinite_quantexp(); test_quantize(); test_quantize_spot(); @@ -223,7 +306,9 @@ int main() test_nonfinite_samequantum(); // decimal_fast32_t normalizes its value in the constructor, // so it will not match the values of the other types - //test_quantexp(); + test_quantexp_fast(); + test_quantexp_samequantum_agreement(); + BOOST_TEST_EQ(quantexp(decimal_fast32_t {3, 2}), -4); test_nonfinite_quantexp(); test_quantize(); // The fast types renormalize the significand to full precision in their @@ -234,6 +319,8 @@ int main() test_same_quantum(); test_nonfinite_samequantum(); test_quantexp(); + test_quantexp_spot(); + test_quantexp_samequantum_agreement(); test_nonfinite_quantexp(); test_quantize(); test_quantize_spot(); @@ -243,7 +330,8 @@ int main() test_nonfinite_samequantum(); // decimal_fast64_t normalizes its value in the constructor, // so it will not match the values of the other types - //test_quantexp(); + test_quantexp_fast(); + test_quantexp_samequantum_agreement(); test_nonfinite_quantexp(); test_quantize(); // The fast types renormalize the significand to full precision in their @@ -254,10 +342,18 @@ int main() test_same_quantum(); test_nonfinite_samequantum(); test_quantexp(); + test_quantexp_spot(); + test_quantexp_samequantum_agreement(); test_nonfinite_quantexp(); test_quantize(); test_quantize_spot(); test_nonfinite_quantize(); + // decimal_fast128_t normalizes its value in the constructor, + // so it will not match the values of the other types + test_quantexp_fast(); + test_quantexp_samequantum_agreement(); + test_nonfinite_quantexp(); + return boost::report_errors(); } From 46e68ff8f8a768ce43c3a547583e8cc8637a957b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 6 Aug 2026 14:04:14 -0400 Subject: [PATCH 2/4] Fix quantexp impls --- include/boost/decimal/decimal128_t.hpp | 2 +- include/boost/decimal/decimal32_t.hpp | 2 +- include/boost/decimal/decimal64_t.hpp | 2 +- include/boost/decimal/decimal_fast128_t.hpp | 2 +- include/boost/decimal/decimal_fast32_t.hpp | 2 +- include/boost/decimal/decimal_fast64_t.hpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index 8f1063652..7f0240949 100644 --- a/include/boost/decimal/decimal128_t.hpp +++ b/include/boost/decimal/decimal128_t.hpp @@ -2285,7 +2285,7 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto quantexpd128(const decimal128_t x) noexcept -> } #endif - return static_cast(x.unbiased_exponent()); + return static_cast(x.biased_exponent()); } BOOST_DECIMAL_CUDA_CONSTEXPR auto copysignd128(decimal128_t mag, const decimal128_t sgn) noexcept -> decimal128_t diff --git a/include/boost/decimal/decimal32_t.hpp b/include/boost/decimal/decimal32_t.hpp index a77ce0f0d..4ad71e9a6 100644 --- a/include/boost/decimal/decimal32_t.hpp +++ b/include/boost/decimal/decimal32_t.hpp @@ -2410,7 +2410,7 @@ constexpr auto quantexpd32(const decimal32_t x) noexcept -> int } #endif - return static_cast(x.unbiased_exponent()); + return static_cast(x.biased_exponent()); } constexpr auto scalblnd32(decimal32_t num, const long exp) noexcept -> decimal32_t diff --git a/include/boost/decimal/decimal64_t.hpp b/include/boost/decimal/decimal64_t.hpp index 75e0396ff..2be8631e0 100644 --- a/include/boost/decimal/decimal64_t.hpp +++ b/include/boost/decimal/decimal64_t.hpp @@ -2322,7 +2322,7 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto quantexpd64(const decimal64_t x) noexcept -> i } #endif - return static_cast(x.unbiased_exponent()); + return static_cast(x.biased_exponent()); } BOOST_DECIMAL_CUDA_CONSTEXPR auto scalblnd64(decimal64_t num, const long exp) noexcept -> decimal64_t diff --git a/include/boost/decimal/decimal_fast128_t.hpp b/include/boost/decimal/decimal_fast128_t.hpp index b0a775e38..89051f3f0 100644 --- a/include/boost/decimal/decimal_fast128_t.hpp +++ b/include/boost/decimal/decimal_fast128_t.hpp @@ -1779,7 +1779,7 @@ constexpr auto quantexpd128f(const decimal_fast128_t& x) noexcept -> int } #endif - return static_cast(x.unbiased_exponent()); + return static_cast(x.biased_exponent()); } #if !defined(BOOST_DECIMAL_DISABLE_CLIB) diff --git a/include/boost/decimal/decimal_fast32_t.hpp b/include/boost/decimal/decimal_fast32_t.hpp index 9ad775ddf..4e0357e8b 100644 --- a/include/boost/decimal/decimal_fast32_t.hpp +++ b/include/boost/decimal/decimal_fast32_t.hpp @@ -1780,7 +1780,7 @@ constexpr auto quantexpd32f(const decimal_fast32_t x) noexcept -> int } #endif - return static_cast(x.unbiased_exponent()); + return static_cast(x.biased_exponent()); } #if !defined(BOOST_DECIMAL_DISABLE_CLIB) diff --git a/include/boost/decimal/decimal_fast64_t.hpp b/include/boost/decimal/decimal_fast64_t.hpp index e952395e1..1c3cd2d84 100644 --- a/include/boost/decimal/decimal_fast64_t.hpp +++ b/include/boost/decimal/decimal_fast64_t.hpp @@ -1785,7 +1785,7 @@ constexpr auto quantexpd64f(const decimal_fast64_t x) noexcept -> int } #endif - return static_cast(x.unbiased_exponent()); + return static_cast(x.biased_exponent()); } constexpr auto scalblnd64f(decimal_fast64_t num, const long exp) noexcept -> decimal_fast64_t From 9c35922ca5c100efbbd5c5e7535e1bdf5ccbe607 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 7 Aug 2026 08:20:21 -0400 Subject: [PATCH 3/4] Readd macro --- test/test_boost_math_univariate_stats.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_boost_math_univariate_stats.cpp b/test/test_boost_math_univariate_stats.cpp index aea003b46..0ebfa6373 100644 --- a/test/test_boost_math_univariate_stats.cpp +++ b/test/test_boost_math_univariate_stats.cpp @@ -9,6 +9,9 @@ // Propogates up from boost.math #define _SILENCE_CXX23_DENORM_DEPRECATION_WARNING +// Needed for operations with boost math +#define BOOST_DECIMAL_ALLOW_IMPLICIT_INTEGER_CONVERSIONS + #include // tag::exclude[] From 7139a18df6ae4a613cb7e3c0443bfa7fe061aca6 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 7 Aug 2026 08:39:15 -0400 Subject: [PATCH 4/4] Fix useless cast warning --- include/boost/decimal/decimal128_t.hpp | 2 +- include/boost/decimal/decimal32_t.hpp | 2 +- include/boost/decimal/decimal64_t.hpp | 2 +- include/boost/decimal/decimal_fast128_t.hpp | 2 +- include/boost/decimal/decimal_fast32_t.hpp | 2 +- include/boost/decimal/decimal_fast64_t.hpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index 7f0240949..f67335b22 100644 --- a/include/boost/decimal/decimal128_t.hpp +++ b/include/boost/decimal/decimal128_t.hpp @@ -2285,7 +2285,7 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto quantexpd128(const decimal128_t x) noexcept -> } #endif - return static_cast(x.biased_exponent()); + return x.biased_exponent(); } BOOST_DECIMAL_CUDA_CONSTEXPR auto copysignd128(decimal128_t mag, const decimal128_t sgn) noexcept -> decimal128_t diff --git a/include/boost/decimal/decimal32_t.hpp b/include/boost/decimal/decimal32_t.hpp index 4ad71e9a6..feb2b1ed7 100644 --- a/include/boost/decimal/decimal32_t.hpp +++ b/include/boost/decimal/decimal32_t.hpp @@ -2410,7 +2410,7 @@ constexpr auto quantexpd32(const decimal32_t x) noexcept -> int } #endif - return static_cast(x.biased_exponent()); + return x.biased_exponent(); } constexpr auto scalblnd32(decimal32_t num, const long exp) noexcept -> decimal32_t diff --git a/include/boost/decimal/decimal64_t.hpp b/include/boost/decimal/decimal64_t.hpp index 2be8631e0..5d03aa5a6 100644 --- a/include/boost/decimal/decimal64_t.hpp +++ b/include/boost/decimal/decimal64_t.hpp @@ -2322,7 +2322,7 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto quantexpd64(const decimal64_t x) noexcept -> i } #endif - return static_cast(x.biased_exponent()); + return x.biased_exponent(); } BOOST_DECIMAL_CUDA_CONSTEXPR auto scalblnd64(decimal64_t num, const long exp) noexcept -> decimal64_t diff --git a/include/boost/decimal/decimal_fast128_t.hpp b/include/boost/decimal/decimal_fast128_t.hpp index 89051f3f0..956ce3f21 100644 --- a/include/boost/decimal/decimal_fast128_t.hpp +++ b/include/boost/decimal/decimal_fast128_t.hpp @@ -1779,7 +1779,7 @@ constexpr auto quantexpd128f(const decimal_fast128_t& x) noexcept -> int } #endif - return static_cast(x.biased_exponent()); + return x.biased_exponent(); } #if !defined(BOOST_DECIMAL_DISABLE_CLIB) diff --git a/include/boost/decimal/decimal_fast32_t.hpp b/include/boost/decimal/decimal_fast32_t.hpp index 4e0357e8b..6bd9cb083 100644 --- a/include/boost/decimal/decimal_fast32_t.hpp +++ b/include/boost/decimal/decimal_fast32_t.hpp @@ -1780,7 +1780,7 @@ constexpr auto quantexpd32f(const decimal_fast32_t x) noexcept -> int } #endif - return static_cast(x.biased_exponent()); + return x.biased_exponent(); } #if !defined(BOOST_DECIMAL_DISABLE_CLIB) diff --git a/include/boost/decimal/decimal_fast64_t.hpp b/include/boost/decimal/decimal_fast64_t.hpp index 1c3cd2d84..993510e40 100644 --- a/include/boost/decimal/decimal_fast64_t.hpp +++ b/include/boost/decimal/decimal_fast64_t.hpp @@ -1785,7 +1785,7 @@ constexpr auto quantexpd64f(const decimal_fast64_t x) noexcept -> int } #endif - return static_cast(x.biased_exponent()); + return x.biased_exponent(); } constexpr auto scalblnd64f(decimal_fast64_t num, const long exp) noexcept -> decimal_fast64_t