[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
Sat Mar 29 23:49:27 PDT 2025


================
@@ -298,6 +301,84 @@ _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)>;
+
+// 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 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;
+}
+
+// An overload for IEEE 754 floating-point numbers
+
+// 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.
+// 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< numeric_limits<_Floating>::is_iec559, int> = 0>
----------------
izvolov wrote:

However, the second method can be improved: first identify the sign bit by XOR-ing two values with opposite signs (like `inf` and `-inf`), then determine the trailing zero count.
But the problem is that `std::bit_cast<uint128>(long double)` still not constexpr.

```cpp
template <std::floating_point F>
constexpr auto bit_width ()
{
    using integer_type = unsigned_representation_for_t<F>;
    constexpr auto inf = std::numeric_limits<F>::infinity();

    // constexpr // error casting to uint128
    auto sign_bit_mask =
        std::bit_cast<integer_type>(-inf) ^
        std::bit_cast<integer_type>(inf);
    return 1 + std::countr_zero(sign_bit_mask);
}

int main()
{
    std::cout << bit_width<float>() << std::endl;
    std::cout << bit_width<double>() << std::endl;
    std::cout << bit_width<long double>() << std::endl;
}
```

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


More information about the libcxx-commits mailing list