From 3584733f4329609a0f01111b672ff0a8fe3cd77f Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Tue, 6 Oct 2026 12:38:29 +0000 Subject: [PATCH 1/4] gaol_interval.h: lexicographic_less, a total order for the containers of the standard library --- gaol/gaol_interval.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/gaol/gaol_interval.h b/gaol/gaol_interval.h index b68c7f9b..58de112a 100644 --- a/gaol/gaol_interval.h +++ b/gaol/gaol_interval.h @@ -2111,6 +2111,43 @@ GAOL_NODISCARD GAOL_INLINE bool operator>(T n, const interval &I) return N.left() == N.right() ? N > I : N >= I; } + /*! + lexicographic_less: a total order on the intervals, for the containers + and the algorithms of the standard library that ask one (std::set, + std::map, std::sort, std::max, std::min, std::clamp...), which < is + not: < is the certainly_le() of IEEE 1788-2015 (strictPrecedes), true + as soon as either interval is empty, so that std::set keeps + no two intervals that overlap and loses every interval once the empty + set is inserted, and std::sort reads past the end of a vector that + holds an empty interval (GAOL v5). + + A class, which the standard library takes as a comparator: + std::set. The empty set comes + first, then the intervals by their left bound and by their right + bound, -oo of a left bound and +oo of a right one included, -0.0 and + +0.0 being equal as bounds. The order is strict: less(I, I) is false, + and two intervals of the same bounds, or two empty sets, are not less + than each other. Specializing std::less for + gaol::interval is left to the program (issue #70): a program passes + gaol::lexicographic_less to its containers explicitly. + */ + struct lexicographic_less { + GAOL_NODISCARD GAOL_INLINE + bool operator()(const interval &I1, const interval &I2) const + { + if (I1.is_empty() || I2.is_empty()) { + return I1.is_empty() && !I2.is_empty(); + } + if (gaol_detail::quiet_less(I1.left(), I2.left())) { + return true; + } + if (gaol_detail::quiet_less(I2.left(), I1.left())) { + return false; + } + return gaol_detail::quiet_less(I1.right(), I2.right()); + } + }; + //@} GAOL_INLINE interval& @@ -2278,6 +2315,7 @@ namespace gaol { using gaol_core::integer; using gaol_core::invabs_rel; using gaol_core::inverse; + using gaol_core::lexicographic_less; using gaol_core::log; using gaol_core::max; using gaol_core::min; From 76ce6d23ef3dd23a1f5888cee00660372f1a9b3b Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Tue, 6 Oct 2026 12:38:29 +0000 Subject: [PATCH 2/4] relations: lexicographic_less tested, std::set and std::sort of an empty interval --- tests/relations.cpp | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/relations.cpp b/tests/relations.cpp index fe993cf2..d77356bf 100644 --- a/tests/relations.cpp +++ b/tests/relations.cpp @@ -2,7 +2,10 @@ // rather than CppUnit (GAOL v5) #include "unit_tests.h" +#include #include +#include +#include class relations_test { public: @@ -239,6 +242,66 @@ class relations_test { } } + /* lexicographic_less (GAOL v5, point V): a total order for the containers + of the standard library. < is strictPrecedes of IEEE 1788-2015, true as + soon as either interval is empty: a std::set built on < loses + the intervals that overlap, and std::sort reads past the end of a vector + that holds an empty interval, which the sort with lexicographic_less() + checks here, an empty interval sorted to its place, at the front of + the vector. */ + void test_lexicographic_less() { + // Strict: no interval is less than itself + const interval xs[] = { interval::emptyset(), interval(-GAOL_INFINITY, 1), + interval(-GAOL_INFINITY, 2), interval::universe(), + interval(-1, 1), interval(0, 1), interval(0, 2), + interval(1, 1), interval(1, 2), + interval(1, GAOL_INFINITY) }; + gaol::lexicographic_less const less; + gaol_core::lexicographic_less const less_core; + for (const interval& x : xs) { + TEST_FALSE(less(x, x)); + TEST_FALSE(less_core(x, x)); + } + for (size_t i = 0; i < sizeof(xs)/sizeof(xs[0]); ++i) { + for (size_t j = 0; j < sizeof(xs)/sizeof(xs[0]); ++j) { + // The expected order of the list above, and a strict order: for two + // different intervals exactly one of less(a, b) and less(b, a), + // for two equal intervals neither + TEST_TRUE(less(xs[i], xs[j]) == (i < j)); + TEST_TRUE(less(xs[i], xs[j]) != less(xs[j], xs[i]) || i == j); + TEST_TRUE(!(less(xs[i], xs[j]) && less(xs[j], xs[i]))); + } + } + // The empty set comes first + TEST_TRUE(less(interval::emptyset(), interval::universe())); + TEST_FALSE(less(interval::emptyset(), interval::emptyset())); + TEST_FALSE(less(interval::universe(), interval::emptyset())); + // -0.0 and +0.0 are equal as bounds + TEST_FALSE(less(interval(-0.0, 0.0), interval(0.0, 0.0))); + TEST_FALSE(less(interval(0.0, 0.0), interval(-0.0, 0.0))); + + // A std::set keeps the intervals that overlap and the empty set + std::set s; + s.insert(interval(0, 1)); + s.insert(interval(0.5, 3)); // overlaps the first: < loses it + s.insert(interval(1, 2)); // overlaps both + s.insert(interval::emptyset()); + TEST_TRUE(s.size() == 4); + TEST_TRUE(s.count(interval(0.5, 3)) == 1); + TEST_TRUE(s.begin()->is_empty()); + // std::sort of a vector holding an empty interval, which reads past the + // end of the vector with < (certainly_le) + std::vector v; + v.push_back(interval(1, 2)); + v.push_back(interval::emptyset()); + v.push_back(interval(-3, 0)); + std::sort(v.begin(), v.end(), less); + TEST_TRUE(v.size() == 3); + TEST_TRUE(v[0].is_empty()); + TEST_TRUE(less(v[1], v[2])); + TEST_TRUE(v[1].set_eq(interval(-3, 0)) && v[2].set_eq(interval(1, 2))); + } + // <-- End of tests }; @@ -248,4 +311,5 @@ GAOL_UNIT_MAIN(relations_test, "relations", GAOL_UNIT_TEST(test_certainly), GAOL_UNIT_TEST(test_symbols_with_double), GAOL_UNIT_TEST(test_symbols_with_integers), + GAOL_UNIT_TEST(test_lexicographic_less), GAOL_UNIT_TEST(test_misc)) From 5b92acc7c0f94a77e8cf9085345b0beb799808e5 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Tue, 6 Oct 2026 12:38:29 +0000 Subject: [PATCH 3/4] tests.md: relations checks gaol::lexicographic_less (GAOL v5, point V) --- doc/tests.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/tests.md b/doc/tests.md index 392c1ba6..4f409398 100644 --- a/doc/tests.md +++ b/doc/tests.md @@ -758,7 +758,13 @@ mode there. operator in both orders, and the relations, `set_contains()` and `set_strictly_contains()` at 2^53 + 1 and 2^64 − 1, which the conversion to a double made wrong; and that an integer that is a double gives what the - double gives. `misc` declares a `namespace detail` of its own, which + double gives. `relations` also checks `gaol::lexicographic_less` + (GAOL v5, point V): a strict total order that puts the empty set first and + the intervals by their bounds, which `<` is not, being the `strictPrecedes` + of IEEE 1788-2015, true as soon as either interval is empty; it sorts a + `std::vector` holding an empty interval, which `<` reads past the end of, + and fills a `std::set` with intervals that overlap, which `<` loses. + `misc` declares a `namespace detail` of its own, which `using namespace gaol` made ambiguous while GAOL's helpers were in `gaol_core::detail`, and checks the names of `gaol`, which names those of `gaol_core` one by one (GAOL v5). `static_assert` checks that `gaol::f` From 02bfa09f228fe5e2fc99612ca0192f8bf260e22e Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Tue, 6 Oct 2026 12:47:20 +0000 Subject: [PATCH 4/4] misc: gaol::lexicographic_less is the class of gaol_core, checked --- tests/misc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/misc.cpp b/tests/misc.cpp index e108ce3a..6b711d80 100644 --- a/tests/misc.cpp +++ b/tests/misc.cpp @@ -73,6 +73,8 @@ namespace gaol_names GAOL_NAMES_CLASS(log2_node); GAOL_NAMES_CLASS(sign_node); GAOL_NAMES_CLASS(trunc_node); + // The comparator of the containers of the standard library (GAOL v5, point V) + GAOL_NAMES_CLASS(lexicographic_less); #undef GAOL_NAMES_CLASS static_assert(same() && gaol::version_major == GAOL_MAJOR_VERSION,