From fc553b86448dcbfef1415cc20f7f0404f4a57953 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 12:43:08 +0100 Subject: [PATCH 01/15] Add outline --- include/boost/decimal/cmath.hpp | 1 + .../decimal/detail/cmath/total_order.hpp | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 include/boost/decimal/detail/cmath/total_order.hpp diff --git a/include/boost/decimal/cmath.hpp b/include/boost/decimal/cmath.hpp index cd96e87c7..0b5f444b9 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 diff --git a/include/boost/decimal/detail/cmath/total_order.hpp b/include/boost/decimal/detail/cmath/total_order.hpp new file mode 100644 index 000000000..5305d724b --- /dev/null +++ b/include/boost/decimal/detail/cmath/total_order.hpp @@ -0,0 +1,69 @@ +// 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 + +namespace boost { +namespace decimal { + +namespace detail { + +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) + { + return !y_neg; + } + // d.3.iii + + } +} + +} // namespace detail + +template +constexpr auto total_order(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 From 1e0402744d42ceada8f5b14676c58a63596c7207 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 15:25:40 +0100 Subject: [PATCH 02/15] Export main template --- include/boost/decimal/detail/cmath/total_order.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/decimal/detail/cmath/total_order.hpp b/include/boost/decimal/detail/cmath/total_order.hpp index 5305d724b..40c88dfeb 100644 --- a/include/boost/decimal/detail/cmath/total_order.hpp +++ b/include/boost/decimal/detail/cmath/total_order.hpp @@ -55,7 +55,7 @@ constexpr auto total_ordering_impl(const T x, const T y) noexcept } // namespace detail -template +BOOST_DECIMAL_EXPORT template constexpr auto total_order(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) { From c732aa7cd225d65dbc3a35fe631a0a137e1cfc4a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 16:31:56 +0100 Subject: [PATCH 03/15] Add missing function --- include/boost/decimal/cmath.hpp | 5 +++++ include/boost/decimal/decimal_fast64_t.hpp | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/boost/decimal/cmath.hpp b/include/boost/decimal/cmath.hpp index 0b5f444b9..33d599f8c 100644 --- a/include/boost/decimal/cmath.hpp +++ b/include/boost/decimal/cmath.hpp @@ -225,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 From c3fdc53aadd4e20de7e48a485f87fca510d6c7e4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 16:32:13 +0100 Subject: [PATCH 04/15] Complete first draft of total ordering --- .../decimal/detail/cmath/total_order.hpp | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/include/boost/decimal/detail/cmath/total_order.hpp b/include/boost/decimal/detail/cmath/total_order.hpp index 40c88dfeb..0555bc9b5 100644 --- a/include/boost/decimal/detail/cmath/total_order.hpp +++ b/include/boost/decimal/detail/cmath/total_order.hpp @@ -5,15 +5,61 @@ #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 { +template +constexpr auto nan_comp(const T x, const bool x_neg, const T y, const bool y_neg) noexcept + BOOST_DECIMAL_REQUIRES_RETURN(detail::is_ieee_type_v, T, bool) +{ + 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; +} + +template +constexpr auto nan_comp(const T, const bool, const T, const bool) noexcept + BOOST_DECIMAL_REQUIRES_RETURN(!detail::is_ieee_type_v, T, bool) +{ + return false; +} + 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) @@ -49,7 +95,47 @@ constexpr auto total_ordering_impl(const T x, const T y) noexcept return !y_neg; } // d.3.iii + // The results here depend on the type being used + // e.g. Fast types don't hold any payload + return nan_comp(x, x_neg, y, y_neg); + } + + 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; + } + } + if (x_neg && y_neg) + { + // c.3.i + return quantexp(x) >= quantexp(y); + } + else + { + // c.3.ii + return quantexp(x) <= quantexp(y); + } } } From e933b0b6fa07ec6eea6d1734fd221fd318e85ba3 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 16:35:28 +0100 Subject: [PATCH 05/15] Test unequal values --- test/Jamfile | 1 + test/test_total_ordering.cpp | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 test/test_total_ordering.cpp 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..e75966317 --- /dev/null +++ b/test/test_total_ordering.cpp @@ -0,0 +1,45 @@ +// 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 + +using namespace boost::decimal; + +static std::mt19937_64 rng(42); +static std::uniform_int_distribution dist(INT_MIN, INT_MAX); +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(total_order(lhs, rhs)); + } + else if (lhs_int > rhs_int) + { + BOOST_TEST(!total_order(lhs, rhs)); + } + } +} + +int main() +{ + test_unequal(); + test_unequal(); + test_unequal(); + + return boost::report_errors(); +} From 8383e13f6ee7bffbb77acc24bd5e767daf442c24 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 16:40:19 +0100 Subject: [PATCH 06/15] Add testing of d.1 and d.2 --- test/test_total_ordering.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/test_total_ordering.cpp b/test/test_total_ordering.cpp index e75966317..88a0e6fb8 100644 --- a/test/test_total_ordering.cpp +++ b/test/test_total_ordering.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include using namespace boost::decimal; @@ -35,11 +36,39 @@ void test_unequal() } } +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(total_order(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(total_order(lhs, rhs)); + } +} + int main() { test_unequal(); test_unequal(); test_unequal(); + test_part_d12(); + test_part_d12(); + test_part_d12(); + return boost::report_errors(); } From 13776d362be54bd178ceda911b1a088ecea26bfd Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 16:54:02 +0100 Subject: [PATCH 07/15] Skip unneeded checks with fast types --- .../decimal/detail/cmath/total_order.hpp | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/include/boost/decimal/detail/cmath/total_order.hpp b/include/boost/decimal/detail/cmath/total_order.hpp index 0555bc9b5..5bcdc1149 100644 --- a/include/boost/decimal/detail/cmath/total_order.hpp +++ b/include/boost/decimal/detail/cmath/total_order.hpp @@ -60,6 +60,11 @@ constexpr auto nan_comp(const T, const bool, const T, const bool) noexcept return false; } +#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) @@ -126,19 +131,31 @@ constexpr auto total_ordering_impl(const T x, const T y) noexcept } } - if (x_neg && y_neg) + BOOST_DECIMAL_IF_CONSTEXPR (detail::is_ieee_type_v) { - // c.3.i - return quantexp(x) >= quantexp(y); + if (x_neg && y_neg) + { + // c.3.i + return quantexp(x) >= quantexp(y); + } + else + { + // c.3.ii + return quantexp(x) <= quantexp(y); + } } else { - // c.3.ii - return quantexp(x) <= quantexp(y); + // Since things are normalized this will always be true + return true; } } } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + } // namespace detail BOOST_DECIMAL_EXPORT template From 5a7400ba2b8074af016f8dd744743a3a9a7ef301 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 3 Nov 2025 16:54:07 +0100 Subject: [PATCH 08/15] Test fast types --- test/test_total_ordering.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_total_ordering.cpp b/test/test_total_ordering.cpp index 88a0e6fb8..ae89b5da7 100644 --- a/test/test_total_ordering.cpp +++ b/test/test_total_ordering.cpp @@ -66,9 +66,17 @@ int main() 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(); + return boost::report_errors(); } From 4a30c92af0f15727e1496a036f589ead9154d86d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 4 Nov 2025 08:42:28 +0100 Subject: [PATCH 09/15] Add d.3.i and d.3.ii tests --- test/test_total_ordering.cpp | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/test_total_ordering.cpp b/test/test_total_ordering.cpp index ae89b5da7..ec728d444 100644 --- a/test/test_total_ordering.cpp +++ b/test/test_total_ordering.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include using namespace boost::decimal; @@ -60,6 +61,44 @@ void test_part_d12() } } +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(total_order(lhs, rhs)); + BOOST_TEST(!total_order(rhs, lhs)); + BOOST_TEST(!total_order(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(total_order(lhs, rhs)); + BOOST_TEST(!total_order(rhs, lhs)); + BOOST_TEST(!total_order(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(!total_order(neg_lhs, neg_rhs)); + BOOST_TEST(total_order(neg_rhs, neg_lhs)); + } +} + int main() { test_unequal(); @@ -78,5 +117,9 @@ int main() test_part_d12(); test_part_d12(); + test_part_d3(); + test_part_d3(); + test_part_d3(); + return boost::report_errors(); } From cbf358f1added8e12d62aef4a6188c2304df26d1 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 4 Nov 2025 08:42:38 +0100 Subject: [PATCH 10/15] Fix ordering of mixed NAN --- include/boost/decimal/detail/cmath/total_order.hpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/boost/decimal/detail/cmath/total_order.hpp b/include/boost/decimal/detail/cmath/total_order.hpp index 5bcdc1149..a5e2d193b 100644 --- a/include/boost/decimal/detail/cmath/total_order.hpp +++ b/include/boost/decimal/detail/cmath/total_order.hpp @@ -95,9 +95,16 @@ constexpr auto total_ordering_impl(const T x, const T y) noexcept // 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 !y_neg; + if (x_signaling && !y_signaling) + { + return !x_neg; + } + else if (!x_signaling && y_signaling) + { + return y_neg; + } } // d.3.iii // The results here depend on the type being used From 61e236232d310d2522cb548626e5783987ddb796 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 4 Nov 2025 12:16:50 +0100 Subject: [PATCH 11/15] Rename function to match prior art --- include/boost/decimal/cmath.hpp | 2 +- .../{total_order.hpp => comparetotal.hpp} | 2 +- test/test_total_ordering.cpp | 24 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) rename include/boost/decimal/detail/cmath/{total_order.hpp => comparetotal.hpp} (98%) diff --git a/include/boost/decimal/cmath.hpp b/include/boost/decimal/cmath.hpp index 33d599f8c..76e6fc110 100644 --- a/include/boost/decimal/cmath.hpp +++ b/include/boost/decimal/cmath.hpp @@ -78,7 +78,7 @@ #include #include #include -#include +#include #include // Macros from 3.6.2 diff --git a/include/boost/decimal/detail/cmath/total_order.hpp b/include/boost/decimal/detail/cmath/comparetotal.hpp similarity index 98% rename from include/boost/decimal/detail/cmath/total_order.hpp rename to include/boost/decimal/detail/cmath/comparetotal.hpp index a5e2d193b..03d589e9a 100644 --- a/include/boost/decimal/detail/cmath/total_order.hpp +++ b/include/boost/decimal/detail/cmath/comparetotal.hpp @@ -166,7 +166,7 @@ constexpr auto total_ordering_impl(const T x, const T y) noexcept } // namespace detail BOOST_DECIMAL_EXPORT template -constexpr auto total_order(const T1 lhs, const T2 rhs) noexcept +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>; diff --git a/test/test_total_ordering.cpp b/test/test_total_ordering.cpp index ec728d444..ef6644186 100644 --- a/test/test_total_ordering.cpp +++ b/test/test_total_ordering.cpp @@ -28,11 +28,11 @@ void test_unequal() if (lhs_int < rhs_int) { - BOOST_TEST(total_order(lhs, rhs)); + BOOST_TEST(comparetotal(lhs, rhs)); } else if (lhs_int > rhs_int) { - BOOST_TEST(!total_order(lhs, rhs)); + BOOST_TEST(!comparetotal(lhs, rhs)); } } } @@ -47,7 +47,7 @@ void test_part_d12() const auto lhs {-std::numeric_limits::quiet_NaN()}; const T rhs {rhs_int}; - BOOST_TEST(total_order(lhs, rhs)); + BOOST_TEST(comparetotal(lhs, rhs)); } for (std::size_t i {}; i < N / 2U; ++i) @@ -57,7 +57,7 @@ void test_part_d12() const T lhs {lhs_int}; const auto rhs {std::numeric_limits::quiet_NaN()}; - BOOST_TEST(total_order(lhs, rhs)); + BOOST_TEST(comparetotal(lhs, rhs)); } } @@ -73,9 +73,9 @@ void test_part_d3() const T lhs {lhs_int * -std::numeric_limits::quiet_NaN()}; const T rhs {rhs_int * std::numeric_limits::quiet_NaN()}; - BOOST_TEST(total_order(lhs, rhs)); - BOOST_TEST(!total_order(rhs, lhs)); - BOOST_TEST(!total_order(rhs, rhs)); + BOOST_TEST(comparetotal(lhs, rhs)); + BOOST_TEST(!comparetotal(rhs, lhs)); + BOOST_TEST(!comparetotal(rhs, rhs)); } // d.3.ii @@ -87,15 +87,15 @@ void test_part_d3() const T lhs {lhs_int * std::numeric_limits::signaling_NaN()}; const T rhs {rhs_int * std::numeric_limits::quiet_NaN()}; - BOOST_TEST(total_order(lhs, rhs)); - BOOST_TEST(!total_order(rhs, lhs)); - BOOST_TEST(!total_order(rhs, rhs)); + 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(!total_order(neg_lhs, neg_rhs)); - BOOST_TEST(total_order(neg_rhs, neg_lhs)); + BOOST_TEST(!comparetotal(neg_lhs, neg_rhs)); + BOOST_TEST(comparetotal(neg_rhs, neg_lhs)); } } From f6e7729c829a0c3cef2bdd366d2e01346a6c1501 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 4 Nov 2025 12:18:01 +0100 Subject: [PATCH 12/15] Add testing of fast types now that they support nan payloads --- test/test_total_ordering.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_total_ordering.cpp b/test/test_total_ordering.cpp index ef6644186..e79c48aaa 100644 --- a/test/test_total_ordering.cpp +++ b/test/test_total_ordering.cpp @@ -121,5 +121,9 @@ int main() test_part_d3(); test_part_d3(); + test_part_d3(); + test_part_d3(); + test_part_d3(); + return boost::report_errors(); } From ba1c5a005e74ce77591975fc915a641f122a184a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 4 Nov 2025 12:32:13 +0100 Subject: [PATCH 13/15] Remove now unneeded type base bifurcation --- .../decimal/detail/cmath/comparetotal.hpp | 64 +++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/include/boost/decimal/detail/cmath/comparetotal.hpp b/include/boost/decimal/detail/cmath/comparetotal.hpp index 03d589e9a..9551f16a1 100644 --- a/include/boost/decimal/detail/cmath/comparetotal.hpp +++ b/include/boost/decimal/detail/cmath/comparetotal.hpp @@ -23,43 +23,6 @@ constexpr auto quantexp(decimal_fast128_t x) noexcept -> int; namespace detail { -template -constexpr auto nan_comp(const T x, const bool x_neg, const T y, const bool y_neg) noexcept - BOOST_DECIMAL_REQUIRES_RETURN(detail::is_ieee_type_v, T, bool) -{ - 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; -} - -template -constexpr auto nan_comp(const T, const bool, const T, const bool) noexcept - BOOST_DECIMAL_REQUIRES_RETURN(!detail::is_ieee_type_v, T, bool) -{ - return false; -} - #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4127) // Conditional expression is constant @@ -107,9 +70,30 @@ constexpr auto total_ordering_impl(const T x, const T y) noexcept } } // d.3.iii - // The results here depend on the type being used - // e.g. Fast types don't hold any payload - return nan_comp(x, x_neg, y, y_neg); + 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) From d216a3c1d6ec81252c281ac1075527ac3385ad87 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 4 Nov 2025 12:35:09 +0100 Subject: [PATCH 14/15] Add testing of path d.3.iii --- test/test_total_ordering.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/test_total_ordering.cpp b/test/test_total_ordering.cpp index e79c48aaa..1dcb0bc21 100644 --- a/test/test_total_ordering.cpp +++ b/test/test_total_ordering.cpp @@ -13,6 +13,7 @@ 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 @@ -97,6 +98,35 @@ void test_part_d3() 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() From 0fb69afa008039b6653b04f93607c757ae1955b0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 4 Nov 2025 13:19:23 +0100 Subject: [PATCH 15/15] Add comparetotal function to cmath documentation page --- doc/modules/ROOT/pages/cmath.adoc | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) 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`