diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index 8f1063652..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.unbiased_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 a77ce0f0d..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.unbiased_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 75e0396ff..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.unbiased_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 b0a775e38..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.unbiased_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 9ad775ddf..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.unbiased_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 e952395e1..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.unbiased_exponent()); + return x.biased_exponent(); } constexpr auto scalblnd64f(decimal_fast64_t num, const long exp) noexcept -> decimal_fast64_t 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[] 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(); }