[libcxx-commits] [libcxx] [libc++] Extend the scope of radix sorting inside std::stable_sort to floating-point types (PR #129452)

Дмитрий Изволов via libcxx-commits libcxx-commits at lists.llvm.org
Wed Mar 26 23:53:45 PDT 2025


https://github.com/izvolov updated https://github.com/llvm/llvm-project/pull/129452

>From 9e06f20417856b79fde4a36f94af7bde4f6f554a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Fri, 31 Jan 2025 01:13:50 +0300
Subject: [PATCH 01/13] sort-scalars

---
 libcxx/include/__algorithm/radix_sort.h  | 97 +++++++++++++++++++++++-
 libcxx/include/__algorithm/stable_sort.h | 19 +++--
 2 files changed, 107 insertions(+), 9 deletions(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index f6d9fb1ad7ca9..a8d355016e77e 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -29,9 +29,11 @@
 
 #include <__algorithm/for_each.h>
 #include <__algorithm/move.h>
+#include <__bit/bit_cast.h>
 #include <__bit/bit_log2.h>
 #include <__bit/countl.h>
 #include <__config>
+#include <__cstddef/size_t.h>
 #include <__functional/identity.h>
 #include <__iterator/access.h>
 #include <__iterator/distance.h>
@@ -44,9 +46,12 @@
 #include <__type_traits/enable_if.h>
 #include <__type_traits/invoke.h>
 #include <__type_traits/is_assignable.h>
+#include <__type_traits/is_enum.h>
+#include <__type_traits/is_floating_point.h>
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_unsigned.h>
 #include <__type_traits/make_unsigned.h>
+#include <__type_traits/underlying_type.h>
 #include <__utility/forward.h>
 #include <__utility/integer_sequence.h>
 #include <__utility/move.h>
@@ -298,6 +303,94 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto __shift_to_unsigned(_Ip __n) {
   return static_cast<make_unsigned_t<_Ip> >(__n ^ __min_value);
 }
 
+template <size_t _Size>
+struct __unsigned_integer_of_size {};
+
+template <>
+struct __unsigned_integer_of_size<1> {
+  using type = uint8_t;
+};
+
+template <>
+struct __unsigned_integer_of_size<2> {
+  using type = uint16_t;
+};
+
+template <>
+struct __unsigned_integer_of_size<4> {
+  using type = uint32_t;
+};
+
+template <>
+struct __unsigned_integer_of_size<8> {
+  using type = uint64_t;
+};
+
+template <>
+struct __unsigned_integer_of_size<16> {
+#  if _LIBCPP_HAS_INT128
+  using type = __int128;
+#  endif
+};
+
+template <size_t _Size>
+using __unsigned_integer_of_size_t _LIBCPP_NODEBUG = typename __unsigned_integer_of_size<_Size>::type;
+
+template <class _Sc>
+using __unsigned_representation_for_t _LIBCPP_NODEBUG = __unsigned_integer_of_size_t<sizeof(_Sc)>;
+
+// Represent a scalar type as an ordered integer
+
+// The function is defined for ordered scalar types: integers, floating-point numbers, pointers, and enums.
+// Returns an integer representation such that for any `x` and `y` such that `x < y`, the expression
+// `__to_ordered_integral(x) < __to_ordered_integral(y)` is true, where `x`, `y` are values of the `Scalar` type.
+// __unsigned_representation_for_t<_Scalar> __to_ordered_integral(_Scalar);
+
+template <class _Integral, enable_if_t< is_integral<_Integral>::value, int> = 0>
+_LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Integral __n) {
+  return __n;
+}
+
+// An overload for floating-point numbers
+
+// From the IEEE 754 standard, we know that:
+// 1. The bit representation of positive floats directly reflects their order:
+//    When comparing floats by magnitude, the number with the larger exponent is greater, and if the exponents are
+//    equal, the one with the larger mantissa is greater.
+// 2. The bit representation of negative floats reflects their reverse order (for the same reasons).
+// 3. The most significant bit (sign bit) is zero for positive floats and one for negative floats. Therefore, in the raw
+//    bit representation, any negative number will be greater than any positive number.
+
+// The only exception from this rule is `NaN`, which is unordered by definition.
+
+// Based on the above, to obtain correctly ordered integral representation of floating-point numbers, we need to:
+// 1. Invert the bit representation (including the sign bit) of negative floats to switch from reverse order to direct
+//    order;
+// 2. Invert the sign bit for positive floats.
+
+// Thus, in final integral representation, we have reversed the order for negative floats and made all negative floats
+// smaller than all positive numbers (by inverting the sign bit).
+template <class _Floating, enable_if_t< is_floating_point<_Floating>::value, int> = 0>
+_LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Floating __f) {
+  using __integral_type          = __unsigned_representation_for_t<_Floating>;
+  constexpr auto __bit_count     = std::numeric_limits<__integral_type>::digits;
+  constexpr auto __sign_bit_mask = static_cast<__integral_type>(__integral_type{1} << (__bit_count - 1));
+
+  const auto __u = std::__bit_cast<__integral_type>(__f);
+
+  return static_cast<__integral_type>(__u & __sign_bit_mask ? ~__u : __u ^ __sign_bit_mask);
+}
+
+template <class _Enum, enable_if_t< is_enum<_Enum>::value, int> = 0>
+_LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Enum __e) {
+  return static_cast<std::underlying_type_t<_Enum>>(__e);
+}
+
+template <class _Pointer>
+_LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Pointer* __ptr) {
+  return std::__bit_cast<__unsigned_representation_for_t<_Pointer*>>(__ptr);
+}
+
 struct __low_byte_fn {
   template <class _Ip>
   _LIBCPP_HIDE_FROM_ABI constexpr uint8_t operator()(_Ip __integer) const {
@@ -314,7 +407,9 @@ __radix_sort(_RandomAccessIterator1 __first,
              _RandomAccessIterator2 __buffer,
              _Map __map,
              _Radix __radix) {
-  auto __map_to_unsigned = [__map = std::move(__map)](const auto& __x) { return std::__shift_to_unsigned(__map(__x)); };
+  auto __map_to_unsigned = [__map = std::move(__map)](const auto& __x) {
+    return std::__shift_to_unsigned(__map(std::__to_ordered_integral(__x)));
+  };
   std::__radix_sort_impl(__first, __last, __buffer, __map_to_unsigned, __radix);
 }
 
diff --git a/libcxx/include/__algorithm/stable_sort.h b/libcxx/include/__algorithm/stable_sort.h
index c7f9780e3f627..b13f44d5ceef0 100644
--- a/libcxx/include/__algorithm/stable_sort.h
+++ b/libcxx/include/__algorithm/stable_sort.h
@@ -25,9 +25,10 @@
 #include <__memory/unique_temporary_buffer.h>
 #include <__type_traits/desugars_to.h>
 #include <__type_traits/enable_if.h>
+#include <__type_traits/invoke.h>
 #include <__type_traits/is_constant_evaluated.h>
-#include <__type_traits/is_integral.h>
 #include <__type_traits/is_same.h>
+#include <__type_traits/is_scalar.h>
 #include <__type_traits/is_trivially_assignable.h>
 #include <__utility/move.h>
 #include <__utility/pair.h>
@@ -201,7 +202,7 @@ struct __stable_sort_switch {
 #if _LIBCPP_STD_VER >= 17
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
-  static_assert(is_integral<_Tp>::value);
+  static_assert(is_scalar<_Tp>::value);
   if constexpr (sizeof(_Tp) == 1) {
     return 1 << 8;
   }
@@ -211,13 +212,14 @@ _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
 
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_max_bound() {
-  static_assert(is_integral<_Tp>::value);
+  static_assert(is_scalar<_Tp>::value);
   if constexpr (sizeof(_Tp) >= 8) {
     return 1 << 15;
   }
 
   return 1 << 16;
 }
+
 #endif // _LIBCPP_STD_VER >= 17
 
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
@@ -245,11 +247,12 @@ _LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
   }
 
 #if _LIBCPP_STD_VER >= 17
-  constexpr auto __default_comp = __desugars_to_v<__totally_ordered_less_tag, _Compare, value_type, value_type >;
-  constexpr auto __integral_value =
-      is_integral_v<value_type > && is_same_v< value_type&, __iter_reference<_RandomAccessIterator>>;
-  constexpr auto __allowed_radix_sort = __default_comp && __integral_value;
-  if constexpr (__allowed_radix_sort) {
+  constexpr auto __default_comp = __desugars_to_v<__less_tag, _Compare, value_type, value_type >;
+  constexpr auto __scalar_value =
+      is_scalar_v<value_type > && is_same_v< value_type&, __iter_reference<_RandomAccessIterator>>;
+  // There are non-comparable scalars (std::nullptr_t, pointers to members), so we need to exclude them.
+  constexpr auto __comparable_value = is_invocable_r_v<bool, _Compare, value_type, value_type>;
+  if constexpr (__default_comp && __scalar_value && __comparable_value) {
     if (__len <= __buff_size && __len >= static_cast<difference_type>(std::__radix_sort_min_bound<value_type>()) &&
         __len <= static_cast<difference_type>(std::__radix_sort_max_bound<value_type>())) {
       if (__libcpp_is_constant_evaluated()) {

>From 2584e157d160c18b2e00f9b52e2fcd96d0d90af8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Wed, 5 Feb 2025 22:59:15 +0300
Subject: [PATCH 02/13] improve-tests-for-floats

---
 .../alg.sort/stable.sort/stable_sort.pass.cpp | 36 ++++++++++++++++++-
 1 file changed, 35 insertions(+), 1 deletion(-)

diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
index 4ee1d795a23b2..d18f956863904 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
@@ -18,6 +18,7 @@
 #include <algorithm>
 #include <cassert>
 #include <iterator>
+#include <limits>
 #include <random>
 #include <vector>
 
@@ -68,6 +69,13 @@ TEST_CONSTEXPR_CXX26 std::vector<T> generate_sawtooth(int N, int M) {
     if (++x == M)
       x = 0;
   }
+
+  if (std::is_signed<T>::value) {
+    for (auto& a : v) {
+      a -= (M / 2);
+    }
+  }
+
   return v;
 }
 
@@ -193,12 +201,38 @@ TEST_CONSTEXPR_CXX26 bool test() {
   return true;
 }
 
+template <class T>
+bool test_floating_special_values() {
+  static_assert(std::is_floating_point<T>::value, "");
+
+  auto v = generate_sawtooth<T>(1024, 512);
+  v.insert(v.end(), 256, static_cast<T>(0.0));
+  v.insert(v.end(), 256, static_cast<T>(-0.0));
+  v.insert(v.end(), 256, std::numeric_limits<T>::infinity());
+  v.insert(v.end(), 256, -std::numeric_limits<T>::infinity());
+
+  std::mt19937 randomness;
+  std::shuffle(v.begin(), v.end(), randomness);
+
+  std::stable_sort(v.begin(), v.end());
+  assert(std::is_sorted(v.begin(), v.end()));
+
+  return true;
+}
+
+template <class T>
+bool test_floating() {
+  return test<T>() && test_floating_special_values<T>();
+}
+
 int main(int, char**) {
   test<int>();
-  test<float>();
+  test_floating<float>();
+  test_floating<double>();
 #if TEST_STD_VER >= 26
   static_assert(test<int>());
   static_assert(test<float>());
+  static_assert(test<double>());
   // test constexprness of radix sort branch
   static_assert(test<char>());
 #endif

>From 811b4562f6bf92916129a5f6a5aecd4c805bf716 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Mon, 3 Mar 2025 21:48:46 +0300
Subject: [PATCH 03/13] arithmetic-only

---
 libcxx/include/__algorithm/radix_sort.h       | 10 ++-------
 libcxx/include/__algorithm/stable_sort.h      | 14 ++++++-------
 .../alg.sort/stable.sort/stable_sort.pass.cpp | 21 +++++++++++++++++++
 3 files changed, 29 insertions(+), 16 deletions(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index a8d355016e77e..ed6215a3bbd85 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -381,15 +381,9 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Floating __f) {
   return static_cast<__integral_type>(__u & __sign_bit_mask ? ~__u : __u ^ __sign_bit_mask);
 }
 
+// There may exist user-defined comparison for enum, so we cannot compare enums just like integers.
 template <class _Enum, enable_if_t< is_enum<_Enum>::value, int> = 0>
-_LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Enum __e) {
-  return static_cast<std::underlying_type_t<_Enum>>(__e);
-}
-
-template <class _Pointer>
-_LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Pointer* __ptr) {
-  return std::__bit_cast<__unsigned_representation_for_t<_Pointer*>>(__ptr);
-}
+_LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Enum __e) = delete;
 
 struct __low_byte_fn {
   template <class _Ip>
diff --git a/libcxx/include/__algorithm/stable_sort.h b/libcxx/include/__algorithm/stable_sort.h
index b13f44d5ceef0..1fb47e7e5570d 100644
--- a/libcxx/include/__algorithm/stable_sort.h
+++ b/libcxx/include/__algorithm/stable_sort.h
@@ -26,9 +26,9 @@
 #include <__type_traits/desugars_to.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/invoke.h>
+#include <__type_traits/is_arithmetic.h>
 #include <__type_traits/is_constant_evaluated.h>
 #include <__type_traits/is_same.h>
-#include <__type_traits/is_scalar.h>
 #include <__type_traits/is_trivially_assignable.h>
 #include <__utility/move.h>
 #include <__utility/pair.h>
@@ -202,7 +202,7 @@ struct __stable_sort_switch {
 #if _LIBCPP_STD_VER >= 17
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
-  static_assert(is_scalar<_Tp>::value);
+  static_assert(is_arithmetic<_Tp>::value);
   if constexpr (sizeof(_Tp) == 1) {
     return 1 << 8;
   }
@@ -212,7 +212,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
 
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_max_bound() {
-  static_assert(is_scalar<_Tp>::value);
+  static_assert(is_arithmetic<_Tp>::value);
   if constexpr (sizeof(_Tp) >= 8) {
     return 1 << 15;
   }
@@ -248,11 +248,9 @@ _LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
 
 #if _LIBCPP_STD_VER >= 17
   constexpr auto __default_comp = __desugars_to_v<__less_tag, _Compare, value_type, value_type >;
-  constexpr auto __scalar_value =
-      is_scalar_v<value_type > && is_same_v< value_type&, __iter_reference<_RandomAccessIterator>>;
-  // There are non-comparable scalars (std::nullptr_t, pointers to members), so we need to exclude them.
-  constexpr auto __comparable_value = is_invocable_r_v<bool, _Compare, value_type, value_type>;
-  if constexpr (__default_comp && __scalar_value && __comparable_value) {
+  constexpr auto __arithmetic_value =
+      is_arithmetic_v<value_type > && is_same_v< value_type&, __iter_reference<_RandomAccessIterator>>;
+  if constexpr (__default_comp && __arithmetic_value) {
     if (__len <= __buff_size && __len >= static_cast<difference_type>(std::__radix_sort_min_bound<value_type>()) &&
         __len <= static_cast<difference_type>(std::__radix_sort_max_bound<value_type>())) {
       if (__libcpp_is_constant_evaluated()) {
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
index d18f956863904..ef150b19dd814 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
@@ -225,10 +225,31 @@ bool test_floating() {
   return test<T>() && test_floating_special_values<T>();
 }
 
+enum struct Enum : int { a, b, c, d, e, f, g, h };
+TEST_CONSTEXPR_CXX26 bool operator<(Enum x, Enum y) { return static_cast<int>(x) > static_cast<int>(y); }
+
+TEST_CONSTEXPR_CXX26 bool test_enum() {
+  auto v = std::vector<Enum>(128, Enum::a);
+  v.resize(v.size() + 128, Enum::b);
+  v.resize(v.size() + 128, Enum::c);
+  v.resize(v.size() + 128, Enum::d);
+  v.resize(v.size() + 128, Enum::e);
+  v.resize(v.size() + 128, Enum::f);
+  v.resize(v.size() + 128, Enum::g);
+  v.resize(v.size() + 128, Enum::h);
+
+  // Order is reversed by definition
+  std::stable_sort(v.begin(), v.end());
+  assert(std::is_sorted(v.begin(), v.end()));
+
+  return true;
+}
+
 int main(int, char**) {
   test<int>();
   test_floating<float>();
   test_floating<double>();
+  test_enum();
 #if TEST_STD_VER >= 26
   static_assert(test<int>());
   static_assert(test<float>());

>From 7c05ede9f483b8d7d55851d6ee358f9741ea7ac9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Mon, 3 Mar 2025 22:36:40 +0300
Subject: [PATCH 04/13] include

---
 .../alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp        | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
index ef150b19dd814..7691b2c5874ea 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
@@ -20,6 +20,7 @@
 #include <iterator>
 #include <limits>
 #include <random>
+#include <type_traits>
 #include <vector>
 
 #include "count_new.h"

>From 88ef132bc0aa99513d421d34fe4d84e87e868ed0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Mon, 3 Mar 2025 23:36:33 +0300
Subject: [PATCH 05/13] include

---
 libcxx/include/__algorithm/radix_sort.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index ed6215a3bbd85..d330bef7a10c6 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -51,7 +51,6 @@
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_unsigned.h>
 #include <__type_traits/make_unsigned.h>
-#include <__type_traits/underlying_type.h>
 #include <__utility/forward.h>
 #include <__utility/integer_sequence.h>
 #include <__utility/move.h>

>From 15069f049fc6cc27ad29f908e180c873e1553aa1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Mon, 3 Mar 2025 23:42:37 +0300
Subject: [PATCH 06/13] exclude-non-ieee-754

---
 libcxx/include/__algorithm/radix_sort.h  | 7 +++----
 libcxx/include/__algorithm/stable_sort.h | 6 ++++--
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index d330bef7a10c6..82b94c2beaced 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -47,7 +47,6 @@
 #include <__type_traits/invoke.h>
 #include <__type_traits/is_assignable.h>
 #include <__type_traits/is_enum.h>
-#include <__type_traits/is_floating_point.h>
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_unsigned.h>
 #include <__type_traits/make_unsigned.h>
@@ -350,9 +349,9 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Integral __n) {
   return __n;
 }
 
-// An overload for floating-point numbers
+// An overload for IEEE 754 floating-point numbers
 
-// From the IEEE 754 standard, we know that:
+// For the floats conforming to IEEE 754 (IEC 559) standard, we know that:
 // 1. The bit representation of positive floats directly reflects their order:
 //    When comparing floats by magnitude, the number with the larger exponent is greater, and if the exponents are
 //    equal, the one with the larger mantissa is greater.
@@ -369,7 +368,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Integral __n) {
 
 // Thus, in final integral representation, we have reversed the order for negative floats and made all negative floats
 // smaller than all positive numbers (by inverting the sign bit).
-template <class _Floating, enable_if_t< is_floating_point<_Floating>::value, int> = 0>
+template <class _Floating, enable_if_t< numeric_limits<_Floating>::is_iec559, int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Floating __f) {
   using __integral_type          = __unsigned_representation_for_t<_Floating>;
   constexpr auto __bit_count     = std::numeric_limits<__integral_type>::digits;
diff --git a/libcxx/include/__algorithm/stable_sort.h b/libcxx/include/__algorithm/stable_sort.h
index 1fb47e7e5570d..322c4dbd57c1b 100644
--- a/libcxx/include/__algorithm/stable_sort.h
+++ b/libcxx/include/__algorithm/stable_sort.h
@@ -26,12 +26,13 @@
 #include <__type_traits/desugars_to.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/invoke.h>
-#include <__type_traits/is_arithmetic.h>
 #include <__type_traits/is_constant_evaluated.h>
+#include <__type_traits/is_integral.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_trivially_assignable.h>
 #include <__utility/move.h>
 #include <__utility/pair.h>
+#include <limits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -249,7 +250,8 @@ _LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
 #if _LIBCPP_STD_VER >= 17
   constexpr auto __default_comp = __desugars_to_v<__less_tag, _Compare, value_type, value_type >;
   constexpr auto __arithmetic_value =
-      is_arithmetic_v<value_type > && is_same_v< value_type&, __iter_reference<_RandomAccessIterator>>;
+      (is_integral_v<value_type > || numeric_limits<value_type>::is_iec559) &&
+      is_same_v< value_type&, __iter_reference<_RandomAccessIterator>>;
   if constexpr (__default_comp && __arithmetic_value) {
     if (__len <= __buff_size && __len >= static_cast<difference_type>(std::__radix_sort_min_bound<value_type>()) &&
         __len <= static_cast<difference_type>(std::__radix_sort_max_bound<value_type>())) {

>From 9d4268ffedabf38bfad0b1da1faaa34587568c1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Wed, 5 Mar 2025 10:46:44 +0300
Subject: [PATCH 07/13] comment

---
 libcxx/include/__algorithm/radix_sort.h | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index 82b94c2beaced..995b1fea7a6bb 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -337,13 +337,9 @@ using __unsigned_integer_of_size_t _LIBCPP_NODEBUG = typename __unsigned_integer
 template <class _Sc>
 using __unsigned_representation_for_t _LIBCPP_NODEBUG = __unsigned_integer_of_size_t<sizeof(_Sc)>;
 
-// Represent a scalar type as an ordered integer
-
-// The function is defined for ordered scalar types: integers, floating-point numbers, pointers, and enums.
+// The function `__to_ordered_integral` is defined for integers and IEEE 754 floating-point numbers.
 // Returns an integer representation such that for any `x` and `y` such that `x < y`, the expression
-// `__to_ordered_integral(x) < __to_ordered_integral(y)` is true, where `x`, `y` are values of the `Scalar` type.
-// __unsigned_representation_for_t<_Scalar> __to_ordered_integral(_Scalar);
-
+// `__to_ordered_integral(x) < __to_ordered_integral(y)` is true, where `x`, `y` are integers or IEEE 754 floats.
 template <class _Integral, enable_if_t< is_integral<_Integral>::value, int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Integral __n) {
   return __n;

>From 91c6bac5ea5ed3571486c9a28f6918394beed3bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Wed, 5 Mar 2025 13:28:33 +0300
Subject: [PATCH 08/13] release-notes

---
 libcxx/docs/ReleaseNotes/21.rst | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst
index 877aa06f8b7e4..12f5b4b86d3c6 100644
--- a/libcxx/docs/ReleaseNotes/21.rst
+++ b/libcxx/docs/ReleaseNotes/21.rst
@@ -60,6 +60,9 @@ Improvements and New Features
 
 - Updated formatting library to Unicode 16.0.0.
 
+- The ``std::stable_sort`` algorithm uses radix sort for floating-point types now, which can improve the performance
+  up to 10 times, depending on type of sorted elements and the initial state of the sorted array.
+
 Deprecations and Removals
 -------------------------
 

>From 5e32fd0be9ea754efe4cf0733eae6f79bc2ba8af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Sat, 8 Mar 2025 17:57:33 +0300
Subject: [PATCH 09/13] tidy

---
 libcxx/include/__algorithm/stable_sort.h | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/libcxx/include/__algorithm/stable_sort.h b/libcxx/include/__algorithm/stable_sort.h
index 322c4dbd57c1b..e70a9c4df84bc 100644
--- a/libcxx/include/__algorithm/stable_sort.h
+++ b/libcxx/include/__algorithm/stable_sort.h
@@ -25,7 +25,6 @@
 #include <__memory/unique_temporary_buffer.h>
 #include <__type_traits/desugars_to.h>
 #include <__type_traits/enable_if.h>
-#include <__type_traits/invoke.h>
 #include <__type_traits/is_constant_evaluated.h>
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_same.h>
@@ -203,7 +202,7 @@ struct __stable_sort_switch {
 #if _LIBCPP_STD_VER >= 17
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
-  static_assert(is_arithmetic<_Tp>::value);
+  static_assert(is_integral_v<_Tp > || numeric_limits<_Tp>::is_iec559);
   if constexpr (sizeof(_Tp) == 1) {
     return 1 << 8;
   }
@@ -213,14 +212,13 @@ _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
 
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_max_bound() {
-  static_assert(is_arithmetic<_Tp>::value);
+  static_assert(is_integral_v<_Tp > || numeric_limits<_Tp>::is_iec559);
   if constexpr (sizeof(_Tp) >= 8) {
     return 1 << 15;
   }
 
   return 1 << 16;
 }
-
 #endif // _LIBCPP_STD_VER >= 17
 
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>

>From 807d73b9d18f19ec4974adc05d0ce6f94402bd12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Tue, 25 Mar 2025 18:31:06 +0300
Subject: [PATCH 10/13] metafunc

---
 libcxx/include/__algorithm/radix_sort.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index 995b1fea7a6bb..49fdb34f0d68a 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -302,7 +302,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto __shift_to_unsigned(_Ip __n) {
 }
 
 template <size_t _Size>
-struct __unsigned_integer_of_size {};
+struct __unsigned_integer_of_size;
 
 template <>
 struct __unsigned_integer_of_size<1> {
@@ -324,12 +324,12 @@ struct __unsigned_integer_of_size<8> {
   using type = uint64_t;
 };
 
+#  if _LIBCPP_HAS_INT128
 template <>
 struct __unsigned_integer_of_size<16> {
-#  if _LIBCPP_HAS_INT128
   using type = __int128;
-#  endif
 };
+#  endif
 
 template <size_t _Size>
 using __unsigned_integer_of_size_t _LIBCPP_NODEBUG = typename __unsigned_integer_of_size<_Size>::type;

>From 4077d33d9bd190ac748143e43b78db2d580fa1d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Tue, 25 Mar 2025 21:45:36 +0300
Subject: [PATCH 11/13] no-debug

---
 libcxx/include/__algorithm/radix_sort.h | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index 49fdb34f0d68a..fb2fcf4a3e3b3 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -306,28 +306,28 @@ struct __unsigned_integer_of_size;
 
 template <>
 struct __unsigned_integer_of_size<1> {
-  using type = uint8_t;
+  using type _LIBCPP_NODEBUG = uint8_t;
 };
 
 template <>
 struct __unsigned_integer_of_size<2> {
-  using type = uint16_t;
+  using type _LIBCPP_NODEBUG = uint16_t;
 };
 
 template <>
 struct __unsigned_integer_of_size<4> {
-  using type = uint32_t;
+  using type _LIBCPP_NODEBUG = uint32_t;
 };
 
 template <>
 struct __unsigned_integer_of_size<8> {
-  using type = uint64_t;
+  using type _LIBCPP_NODEBUG = uint64_t;
 };
 
 #  if _LIBCPP_HAS_INT128
 template <>
 struct __unsigned_integer_of_size<16> {
-  using type = __int128;
+  using type _LIBCPP_NODEBUG = __int128;
 };
 #  endif
 

>From b5955b42eeca6589d142a6db57a50909eac98ee9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Wed, 26 Mar 2025 16:42:57 +0300
Subject: [PATCH 12/13] unsigned-int128

---
 libcxx/include/__algorithm/radix_sort.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index fb2fcf4a3e3b3..eb0caeb579f24 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -327,7 +327,7 @@ struct __unsigned_integer_of_size<8> {
 #  if _LIBCPP_HAS_INT128
 template <>
 struct __unsigned_integer_of_size<16> {
-  using type _LIBCPP_NODEBUG = __int128;
+  using type _LIBCPP_NODEBUG = unsigned __int128;
 };
 #  endif
 

>From 0a0513d6367db3a31e361a4f3404dbbb6be99587 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=98=D0=B7?=
 =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2?= <dmitriy at izvolov.ru>
Date: Thu, 27 Mar 2025 00:07:34 +0300
Subject: [PATCH 13/13] long-double

---
 libcxx/include/__algorithm/radix_sort.h        | 18 +++++++++++++++++-
 libcxx/include/__algorithm/stable_sort.h       |  6 +++---
 .../alg.sort/stable.sort/stable_sort.pass.cpp  |  1 +
 3 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/libcxx/include/__algorithm/radix_sort.h b/libcxx/include/__algorithm/radix_sort.h
index eb0caeb579f24..d1e160568e6af 100644
--- a/libcxx/include/__algorithm/radix_sort.h
+++ b/libcxx/include/__algorithm/radix_sort.h
@@ -31,7 +31,6 @@
 #include <__algorithm/move.h>
 #include <__bit/bit_cast.h>
 #include <__bit/bit_log2.h>
-#include <__bit/countl.h>
 #include <__config>
 #include <__cstddef/size_t.h>
 #include <__functional/identity.h>
@@ -44,12 +43,15 @@
 #include <__numeric/partial_sum.h>
 #include <__type_traits/decay.h>
 #include <__type_traits/enable_if.h>
+#include <__type_traits/integral_constant.h>
 #include <__type_traits/invoke.h>
 #include <__type_traits/is_assignable.h>
 #include <__type_traits/is_enum.h>
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_unsigned.h>
 #include <__type_traits/make_unsigned.h>
+#include <__type_traits/void_t.h>
+#include <__utility/declval.h>
 #include <__utility/forward.h>
 #include <__utility/integer_sequence.h>
 #include <__utility/move.h>
@@ -379,6 +381,20 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Floating __f) {
 template <class _Enum, enable_if_t< is_enum<_Enum>::value, int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(_Enum __e) = delete;
 
+// `long double` varies significantly across platforms and compilers, making it practically
+// impossible to determine its actual bit width for conversion to an ordered integer.
+inline _LIBCPP_HIDE_FROM_ABI constexpr auto __to_ordered_integral(long double) = delete;
+
+template <class _Tp, class = void>
+struct __is_ordered_integer_representable : false_type {};
+
+template <class _Tp>
+struct __is_ordered_integer_representable<_Tp, __void_t<decltype(std::__to_ordered_integral(std::declval<_Tp>()))>>
+    : true_type {};
+
+template <class _Tp>
+inline constexpr auto __is_ordered_integer_representable_v = __is_ordered_integer_representable<_Tp>::value;
+
 struct __low_byte_fn {
   template <class _Ip>
   _LIBCPP_HIDE_FROM_ABI constexpr uint8_t operator()(_Ip __integer) const {
diff --git a/libcxx/include/__algorithm/stable_sort.h b/libcxx/include/__algorithm/stable_sort.h
index e70a9c4df84bc..d193006efb749 100644
--- a/libcxx/include/__algorithm/stable_sort.h
+++ b/libcxx/include/__algorithm/stable_sort.h
@@ -202,7 +202,7 @@ struct __stable_sort_switch {
 #if _LIBCPP_STD_VER >= 17
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
-  static_assert(is_integral_v<_Tp > || numeric_limits<_Tp>::is_iec559);
+  static_assert(__is_ordered_integer_representable_v<_Tp>);
   if constexpr (sizeof(_Tp) == 1) {
     return 1 << 8;
   }
@@ -212,7 +212,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_min_bound() {
 
 template <class _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr unsigned __radix_sort_max_bound() {
-  static_assert(is_integral_v<_Tp > || numeric_limits<_Tp>::is_iec559);
+  static_assert(__is_ordered_integer_representable_v<_Tp>);
   if constexpr (sizeof(_Tp) >= 8) {
     return 1 << 15;
   }
@@ -248,7 +248,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
 #if _LIBCPP_STD_VER >= 17
   constexpr auto __default_comp = __desugars_to_v<__less_tag, _Compare, value_type, value_type >;
   constexpr auto __arithmetic_value =
-      (is_integral_v<value_type > || numeric_limits<value_type>::is_iec559) &&
+      __is_ordered_integer_representable_v<value_type> &&
       is_same_v< value_type&, __iter_reference<_RandomAccessIterator>>;
   if constexpr (__default_comp && __arithmetic_value) {
     if (__len <= __buff_size && __len >= static_cast<difference_type>(std::__radix_sort_min_bound<value_type>()) &&
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
index 7691b2c5874ea..e05457492db32 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
@@ -250,6 +250,7 @@ int main(int, char**) {
   test<int>();
   test_floating<float>();
   test_floating<double>();
+  test_floating<long double>();
   test_enum();
 #if TEST_STD_VER >= 26
   static_assert(test<int>());



More information about the libcxx-commits mailing list