diff --git a/doc/modules/ROOT/pages/cmath.adoc b/doc/modules/ROOT/pages/cmath.adoc index 4df937e19..31b62b1bc 100644 --- a/doc/modules/ROOT/pages/cmath.adoc +++ b/doc/modules/ROOT/pages/cmath.adoc @@ -702,3 +702,45 @@ The function returns the decimal type with number of fractional digits equal to `rescale` is similar to https://en.cppreference.com/w/cpp/numeric/math/trunc[trunc], and with the default precision argument of 0 it is identical. NOTE: This function was previously known as `trunc_to` which was deprecated in v5.0.0 and removed in v6.0.0 + +=== `comparetotal` + +[source, c++] +---- +#include + +namespace boost { +namespace decimal { + +template +constexpr bool comparetotal(DecimalType x, DecimalType y) noexcept; + +} // namespace decimal +} // namespace boost +---- + +This function is an extended version of normal ordering to take into account payloads of NANs, and canonical forms. + +Effects: + +. If x < y returns `true` + +. If x > y returns `false` + +. If x == y: +.. -0 < +0 is `true` +.. +0 < -0 is `false` +.. If x and y have the same sign: +... If x and y are both negative, `comparetotal(x, y)` is `true` IFF the exponent of x pass:[>=] exponent of y +... Otherwise, `comparetotal(x, y)` is `true` IFF the exponent of x pass:[<=] y + +. If x and y are unordered because x or y is a NAN: +.. `comparetotal(-NAN, y)` is `true` +.. `comparetotal(x, +NAN)` is `true` +.. If x and y are both NAN: +... `comparetotal(-NAN, +NAN)` returns `true` +... `comparetotal(+SNAN, +QNAN)` returns `true` +... `comparetotal(-QNAN, -SNAN)` returns `true` +... `comparetotal(NAN, NAN)` +.... If both `NAN` are positive returns `true` if the payload of `x` is less than the payload of `y` +.... If both `NAN` are negative returns `true` if the payload of `x` is greater than the payload of `y` diff --git a/include/boost/decimal/cmath.hpp b/include/boost/decimal/cmath.hpp index cd96e87c7..76e6fc110 100644 --- a/include/boost/decimal/cmath.hpp +++ b/include/boost/decimal/cmath.hpp @@ -78,6 +78,7 @@ #include #include #include +#include #include // Macros from 3.6.2 @@ -224,6 +225,11 @@ BOOST_DECIMAL_EXPORT constexpr auto quantexp(decimal64_t x) noexcept -> int return quantexpd64(x); } +BOOST_DECIMAL_EXPORT constexpr auto quantexp(decimal_fast64_t x) noexcept -> int +{ + return quantexpd64f(x); +} + BOOST_DECIMAL_EXPORT constexpr auto quantexp(decimal128_t x) noexcept -> int { return quantexpd128(x); diff --git a/include/boost/decimal/decimal_fast64_t.hpp b/include/boost/decimal/decimal_fast64_t.hpp index 91011a673..a7ba82332 100644 --- a/include/boost/decimal/decimal_fast64_t.hpp +++ b/include/boost/decimal/decimal_fast64_t.hpp @@ -476,6 +476,7 @@ BOOST_DECIMAL_EXPORT class decimal_fast64_t final friend constexpr auto copysignd64f(decimal_fast64_t mag, decimal_fast64_t sgn) noexcept -> decimal_fast64_t; friend constexpr auto scalbnd64f(decimal_fast64_t num, int exp) noexcept -> decimal_fast64_t; friend constexpr auto scalblnd64f(decimal_fast64_t num, long exp) noexcept -> decimal_fast64_t; + friend constexpr auto quantexpd64f(decimal_fast64_t x) noexcept -> int; }; #ifdef BOOST_DECIMAL_HAS_CONCEPTS @@ -1471,6 +1472,20 @@ constexpr auto decimal_fast64_t::operator--(int) noexcept -> decimal_fast64_t& return --(*this); } +// Effects: if x is finite, returns its quantum exponent. +// Otherwise, a domain error occurs and INT_MIN is returned. +constexpr auto quantexpd64f(const decimal_fast64_t x) noexcept -> int +{ + #ifndef BOOST_DECIMAL_FAST_MATH + if (!isfinite(x)) + { + return INT_MIN; + } + #endif + + return static_cast(x.unbiased_exponent()); +} + constexpr auto scalblnd64f(decimal_fast64_t num, const long exp) noexcept -> decimal_fast64_t { #ifndef BOOST_DECIMAL_FAST_MATH diff --git a/include/boost/decimal/detail/cmath/comparetotal.hpp b/include/boost/decimal/detail/cmath/comparetotal.hpp new file mode 100644 index 000000000..9551f16a1 --- /dev/null +++ b/include/boost/decimal/detail/cmath/comparetotal.hpp @@ -0,0 +1,163 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_DECIMAL_DETAIL_CMATH_TOTAL_ORDER_HPP +#define BOOST_DECIMAL_DETAIL_CMATH_TOTAL_ORDER_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace decimal { + +constexpr auto quantexp(decimal32_t x) noexcept -> int; +constexpr auto quantexp(decimal_fast32_t x) noexcept -> int; +constexpr auto quantexp(decimal64_t x) noexcept -> int; +constexpr auto quantexp(decimal_fast64_t x) noexcept -> int; +constexpr auto quantexp(decimal128_t x) noexcept -> int; +constexpr auto quantexp(decimal_fast128_t x) noexcept -> int; + +namespace detail { + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4127) // Conditional expression is constant +#endif + +template +constexpr auto total_ordering_impl(const T x, const T y) noexcept + BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, T, bool) +{ + // Part d: Check for unordered values + const auto x_nan {isnan(x)}; + const auto x_neg {signbit(x)}; + const auto y_nan {isnan(y)}; + const auto y_neg {signbit(y)}; + + if (x_nan && x_neg && !y_nan) + { + // d.1 + return true; + } + if (!x_nan && y_nan && !y_neg) + { + // d.2 + return true; + } + if (x_nan && y_nan) + { + // d.3.i + if (x_neg && !y_neg) + { + return true; + } + // d.3.ii + const auto x_signaling {issignaling(x)}; + const auto y_signaling {issignaling(y)}; + if (x_signaling || y_signaling) + { + if (x_signaling && !y_signaling) + { + return !x_neg; + } + else if (!x_signaling && y_signaling) + { + return y_neg; + } + } + // d.3.iii + const auto x_payload {read_payload(x)}; + const auto y_payload {read_payload(y)}; + + if (x_payload != y_payload) + { + if (!x_neg && !y_neg) + { + return x_payload < y_payload; + } + else if (x_neg && y_neg) + { + return x_payload > y_payload; + } + else if (x_neg && !y_neg) + { + return true; + } + else + { + return false; + } + } + + return false; + } + + if (x < y) + { + // part a + return true; + } + else if (x > y) + { + // part b + return false; + } + else + { + if (x == 0 && y == 0) + { + if (x_neg && !y_neg) + { + // c.1 + return true; + } + else if (!x_neg && y_neg) + { + // c.2 + return false; + } + } + + BOOST_DECIMAL_IF_CONSTEXPR (detail::is_ieee_type_v) + { + if (x_neg && y_neg) + { + // c.3.i + return quantexp(x) >= quantexp(y); + } + else + { + // c.3.ii + return quantexp(x) <= quantexp(y); + } + } + else + { + // Since things are normalized this will always be true + return true; + } + } +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace detail + +BOOST_DECIMAL_EXPORT template +constexpr auto comparetotal(const T1 lhs, const T2 rhs) noexcept + BOOST_DECIMAL_REQUIRES_TWO_RETURN(detail::is_decimal_floating_point_v, T1, detail::is_decimal_floating_point_v, T2, bool) +{ + using larger_type = std::conditional_t >= detail::decimal_val_v, T1, T2>; + return detail::total_ordering_impl(static_cast(lhs), static_cast(rhs)); +} + +} // namespace decimal +} // namespace boost + +#endif // BOOST_DECIMAL_DETAIL_CMATH_TOTAL_ORDER_HPP diff --git a/test/Jamfile b/test/Jamfile index 514c61f65..591c0cc7b 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -184,6 +184,7 @@ run test_tanh.cpp ; run test_tgamma.cpp ; run test_to_chars.cpp ; run test_to_string.cpp ; +run test_total_ordering.cpp ; run test_zeta.cpp ; # Run the examples too diff --git a/test/test_total_ordering.cpp b/test/test_total_ordering.cpp new file mode 100644 index 000000000..1dcb0bc21 --- /dev/null +++ b/test/test_total_ordering.cpp @@ -0,0 +1,159 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include +#include +#include + +using namespace boost::decimal; + +static std::mt19937_64 rng(42); +static std::uniform_int_distribution dist(INT_MIN, INT_MAX); +static std::uniform_int_distribution payload_dist(0U, 10U); +static constexpr std::size_t N {1024}; + +template +void test_unequal() +{ + for (std::size_t i {}; i < N; ++i) + { + const auto lhs_int {dist(rng)}; + const auto rhs_int {dist(rng)}; + + const T lhs {lhs_int}; + const T rhs {rhs_int}; + + if (lhs_int < rhs_int) + { + BOOST_TEST(comparetotal(lhs, rhs)); + } + else if (lhs_int > rhs_int) + { + BOOST_TEST(!comparetotal(lhs, rhs)); + } + } +} + +template +void test_part_d12() +{ + for (std::size_t i {}; i < N / 2U; ++i) + { + const auto rhs_int {dist(rng)}; + + const auto lhs {-std::numeric_limits::quiet_NaN()}; + const T rhs {rhs_int}; + + BOOST_TEST(comparetotal(lhs, rhs)); + } + + for (std::size_t i {}; i < N / 2U; ++i) + { + const auto lhs_int {dist(rng)}; + + const T lhs {lhs_int}; + const auto rhs {std::numeric_limits::quiet_NaN()}; + + BOOST_TEST(comparetotal(lhs, rhs)); + } +} + +template +void test_part_d3() +{ + // d.3.i + for (std::size_t i {}; i < N / 3; ++i) + { + const auto lhs_int {dist(rng)}; + const auto rhs_int {dist(rng)}; + + const T lhs {lhs_int * -std::numeric_limits::quiet_NaN()}; + const T rhs {rhs_int * std::numeric_limits::quiet_NaN()}; + + BOOST_TEST(comparetotal(lhs, rhs)); + BOOST_TEST(!comparetotal(rhs, lhs)); + BOOST_TEST(!comparetotal(rhs, rhs)); + } + + // d.3.ii + for (std::size_t i {}; i < N / 3; ++i) + { + const auto lhs_int {dist(rng)}; + const auto rhs_int {dist(rng)}; + + const T lhs {lhs_int * std::numeric_limits::signaling_NaN()}; + const T rhs {rhs_int * std::numeric_limits::quiet_NaN()}; + + BOOST_TEST(comparetotal(lhs, rhs)); + BOOST_TEST(!comparetotal(rhs, lhs)); + BOOST_TEST(!comparetotal(rhs, rhs)); + + const T neg_lhs {lhs_int * -std::numeric_limits::signaling_NaN()}; + const T neg_rhs {rhs_int * -std::numeric_limits::quiet_NaN()}; + + BOOST_TEST(!comparetotal(neg_lhs, neg_rhs)); + BOOST_TEST(comparetotal(neg_rhs, neg_lhs)); + } + + // d.3.iii + for (std::size_t i {}; i < N / 3; ++i) + { + const auto lhs_int {payload_dist(rng)}; + const auto rhs_int {payload_dist(rng)}; + + const auto lhs_int_string {std::to_string(lhs_int)}; + const auto rhs_int_string {std::to_string(rhs_int)}; + + const auto lhs {nan(lhs_int_string.c_str())}; + const auto rhs {nan(rhs_int_string.c_str())}; + + if (lhs_int < rhs_int) + { + BOOST_TEST(comparetotal(lhs, rhs)); + BOOST_TEST(!comparetotal(rhs, lhs)); + } + else if (lhs_int > rhs_int) + { + BOOST_TEST(!comparetotal(lhs, rhs)); + BOOST_TEST(comparetotal(rhs, lhs)); + } + else + { + BOOST_TEST(!comparetotal(lhs, rhs)); + BOOST_TEST(!comparetotal(rhs, lhs)); + } + } +} + +int main() +{ + test_unequal(); + test_unequal(); + test_unequal(); + + test_unequal(); + test_unequal(); + test_unequal(); + + test_part_d12(); + test_part_d12(); + test_part_d12(); + + test_part_d12(); + test_part_d12(); + test_part_d12(); + + test_part_d3(); + test_part_d3(); + test_part_d3(); + + test_part_d3(); + test_part_d3(); + test_part_d3(); + + return boost::report_errors(); +}