public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* IEC559 totalOrder
@ 2019-11-09  7:40 Matthias Kretz
  2019-11-13 22:03 ` Jonathan Wakely
  0 siblings, 1 reply; 3+ messages in thread
From: Matthias Kretz @ 2019-11-09  7:40 UTC (permalink / raw)
  To: libstdc++

Here's an IEC559 totalOrder implementation that works with clang and GCC, for 
float, double, long double, and __float128 (at least the non-clang branch 
should work with float16, too):
https://godbolt.org/z/QzGWdB

Note that I simplified the order reversal of negative values slightly, and 
producing the bitmask for the sign is now "simpler".

As Jakub noted on IRC, 32-bit x86's long double is a problem. Since it has 
sizeof == 12 there's no *constexpr* way to cast it to an integer. 
std::bit_cast also wouldn't help. It seems there's no way around a builtin for 
this function. (Besides that, I ignored the presence of unnormal values in 
long double - you still get a totalOrder, but I'm not sure IEC559 requests a 
different behavior.)

Cheers,
  Matthias

--

#include <type_traits>
#include <limits>
#include <climits>

template <class T>
  constexpr bool totalOrder(T xx, T yy)
{
  //static_assert(std::numeric_limits<T>::is_iec559); // fails for __float128
  constexpr int Bytes = sizeof(T) == 12 ? 16 : sizeof(T);
  using II = std::conditional_t<Bytes == sizeof(signed char), signed char,
             std::conditional_t<Bytes == sizeof(signed short), signed short,
             std::conditional_t<Bytes == sizeof(signed int), signed int,
             std::conditional_t<Bytes == sizeof(signed long), signed long,
             std::conditional_t<Bytes == sizeof(signed long long), signed long 
long,
                                __int128>>>>>;
#ifdef __clang__
  constexpr int unused_bits = sizeof(T) <= 8 ? 0 : 
                            (Bytes - 8) * CHAR_BIT - 1 -
                            __builtin_ctzll((__builtin_bit_cast(II, T(-0.)) >> 
8 * CHAR_BIT));
  const II xi = __builtin_bit_cast(II, xx) << unused_bits;
  const II yi = __builtin_bit_cast(II, yy) << unused_bits;
  constexpr II signbit = __builtin_bit_cast(II, T(-0.)) << unused_bits;
  const auto x = xi < 0 ? ~(xi ^ signbit) : xi;
  const auto y = yi < 0 ? ~(yi ^ signbit) : yi;
  return x <= y;
#else
  using F [[gnu::vector_size(Bytes)]] = T;
  using I [[gnu::vector_size(Bytes)]] = II;
  constexpr int unused_bits = sizeof(T) <= 8 ? 0 : 
                            (Bytes - 8) * CHAR_BIT - 1 -
                            __builtin_ctzll((reinterpret_cast<I>(F{T(-0.)}) >> 
8 * CHAR_BIT)[0]);
  const I xi = reinterpret_cast<I>(F{xx}) << unused_bits;
  const I yi = reinterpret_cast<I>(F{yy}) << unused_bits;
  constexpr I signbit = reinterpret_cast<I>(F{T(-0.)}) << unused_bits;
  const auto x = xi[0] < 0 ? (~(xi ^ signbit))[0] : xi[0];
  const auto y = yi[0] < 0 ? (~(yi ^ signbit))[0] : yi[0];
  return x <= y;
#endif
}

template <typename T> struct Test
{
  // numeric_limits<__float128> produces bogus values
  static constexpr auto qnan = std::numeric_limits<double>::quiet_NaN();
  static constexpr auto snan = std::numeric_limits<double>::signaling_NaN();
  static constexpr auto inf  = std::numeric_limits<double>::infinity();
  
  static_assert( totalOrder<T>( 0.0,  0.0));
  static_assert( totalOrder<T>( 1.0,  1.0));
  static_assert( totalOrder<T>(-1.0, -1.0));
  static_assert( totalOrder<T>(-0.0, -0.0));
  static_assert( totalOrder<T>(qnan, qnan));
  static_assert( totalOrder<T>(snan, snan));
  static_assert( totalOrder<T>( inf,  inf));
  static_assert( totalOrder<T>(-0.0,  0.0));
  static_assert( totalOrder<T>(-0.0, +1.0));
  static_assert( totalOrder<T>(-qnan, +0.0));
  static_assert( totalOrder<T>(-qnan, -inf));
  static_assert( totalOrder<T>(-3., -2.));
  static_assert( totalOrder<T>(2., 3.));
  static_assert( totalOrder<T>(2., inf));
  static_assert( totalOrder<T>(0., qnan));
  static_assert( totalOrder<T>(0., 2.));
  static_assert( totalOrder<T>(2., qnan));
  static_assert( totalOrder<T>(inf, qnan));
  static_assert( totalOrder<T>(snan, qnan));
  static_assert(!totalOrder<T>(snan, -qnan));
  static_assert(!totalOrder<T>(0.0, -0.0));
  static_assert(!totalOrder<T>(0.0, -qnan));
  static_assert(!totalOrder<T>(0.0, -1.0));
  static_assert(!totalOrder<T>(1.0, -1.0));
  static_assert(!totalOrder<T>(qnan, -inf));
};

Test<float> a;
Test<double> b;
Test<long double> c;
Test<__float128> d;

-- 
──────────────────────────────────────────────────────────────────────────
 Dr. Matthias Kretz                           https://mattkretz.github.io
 GSI Helmholtz Centre for Heavy Ion Research               https://gsi.de
 std::experimental::simd              https://github.com/VcDevel/std-simd
──────────────────────────────────────────────────────────────────────────



^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: IEC559 totalOrder
  2019-11-09  7:40 IEC559 totalOrder Matthias Kretz
@ 2019-11-13 22:03 ` Jonathan Wakely
  2019-11-13 22:13   ` Jonathan Wakely
  0 siblings, 1 reply; 3+ messages in thread
From: Jonathan Wakely @ 2019-11-13 22:03 UTC (permalink / raw)
  To: Matthias Kretz; +Cc: libstdc++

[-- Attachment #1: Type: text/plain, Size: 6829 bytes --]

On 09/11/19 08:40 +0100, Matthias Kretz wrote:
>Here's an IEC559 totalOrder implementation that works with clang and GCC, for
>float, double, long double, and __float128 (at least the non-clang branch
>should work with float16, too):
>https://godbolt.org/z/QzGWdB
>
>Note that I simplified the order reversal of negative values slightly, and
>producing the bitmask for the sign is now "simpler".
>
>As Jakub noted on IRC, 32-bit x86's long double is a problem. Since it has
>sizeof == 12 there's no *constexpr* way to cast it to an integer.
>std::bit_cast also wouldn't help. It seems there's no way around a builtin for
>this function. (Besides that, I ignored the presence of unnormal values in
>long double - you still get a totalOrder, but I'm not sure IEC559 requests a
>different behavior.)

This is indistinguishable from magic, thanks!

I've reworked your totalOrder as follows:

  template<floating_point _Fp> requires is_iec559<_Fp>
    constexpr strong_ordering
    __fp_strong_order(_Fp __e, _Fp __f)
    {
      struct _Selector : __make_unsigned_selector_base
      {
        using _Ints = _List<signed char, signed short, signed int,
              signed long, signed long long
#ifdef __GLIBCXX_TYPE_INT_N_0
                , signed __GLIBCXX_TYPE_INT_N_0
#endif
                >;
        using type = typename __select<sizeof(_Fp), _Ints>::__type;
      };
      using _Si = _Selector::type;

      auto __bit_cast = [](_Fp __x) -> _Si {
#ifdef __clang__
          return __builtin_bit_cast(_Si, __x);
#else
          using _Fp_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Fp;
          using _Si_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Si;
          return reinterpret_cast<_Si_v>(_Fp_v{__x})[0];
#endif
      };

      constexpr int __unused_bits = sizeof(_Fp) <= 8
        ? 0
        : (sizeof(_Fp) - 8) * __CHAR_BIT__ - 1
          - __builtin_ctzll(_Si(__bit_cast(_Fp(-0.)) >> 8 * __CHAR_BIT__));
      constexpr _Si __signbit = __bit_cast(_Fp(-0.)) << __unused_bits;
      const _Si __ei = __bit_cast(__e) << __unused_bits;
      const _Si __fi = __bit_cast(__f) << __unused_bits;
      const _Si __e_cmp = __ei < 0 ? (~_Si(__ei ^ __signbit)) : __ei;
      const _Si __f_cmp = __fi < 0 ? (~_Si(__fi ^ __signbit)) : __fi;
      return __e_cmp <=> __f_cmp;
    }

And then for the testsuite we can define totalOrder as:

  template<typename T>
    constexpr bool totalOrder(T e, T f)
    {
      return is_lteq(std::strong_order(e, f));
    }

I *think* I've got casts in the right places in __fp_strong_order to
avoid problems with integer promotions when sizeof(_Fp) < sizeof(int),
so that it's safe to immediately do [0] on the vector type to convert
back to a scalar.

Does that look OK? Have I broken it somehow?

I've also defined this, for the non-IEEE 60559 case:

    template<typename _Tp>
      concept is_iec559 = floating_point<_Tp>
	&& numeric_limits<_Tp>::is_iec559
	// Exclude x86 12-byte long double:
	&& ((sizeof(_Tp) & (sizeof(_Tp) - 1)) == 0);

    template<floating_point _Fp> requires (!is_iec559<_Fp>)
      constexpr strong_ordering
      __fp_strong_order(_Fp __e, _Fp __f)
      {
	auto __pord = __e <=> __f;
	if (is_lt(__pord))
	  return strong_ordering::less;
	else if (is_gt(__pord))
	  return strong_ordering::greater;
	else if (is_eq(__pord))
	  return strong_ordering::equal;
	else
	  return strong_ordering::equivalent;
      }

A patch against current trunk is attached, although it fails some
__float128 tests, because is_iec559 is false for that type:

.../testsuite/18_support/comparisons/algorithms/strong_order.cc: In instantiation of 'struct Test<__float128>':
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:128:   required from here
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:107: error: non-constant condition for static assertion
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:107:   in 'constexpr' expansion of 'totalOrder<__float128>(((__float128)(- Test<__float128>::qnan)), ((__float128)(+0.0)))'
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:88:   in 'constexpr' expansion of 'std::__cmp_alg::strong_order.std::__cmp_cust::_Strong_order::operator()<__float128&, __float128&>(e, f)'
.../libsupc++/compare:731:   in 'constexpr' expansion of 'std::__cmp_cust::__fp_strong_order<__float128>(__e, __f)'
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:107: error: '(-QNaNf128 < 0.0f128)' is not a constant expression
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:108: error: non-constant condition for static assertion
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:108:   in 'constexpr' expansion of 'totalOrder<__float128>(((__float128)(- Test<__float128>::qnan)), ((__float128)(- Test<__float128>::inf)))'
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:88:   in 'constexpr' expansion of 'std::__cmp_alg::strong_order.std::__cmp_cust::_Strong_order::operator()<__float128&, __float128&>(e, f)'
.../libsupc++/compare:731:   in 'constexpr' expansion of 'std::__cmp_cust::__fp_strong_order<__float128>(__e, __f)'
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:108: error: '(-QNaNf128 < -Inff128)' is not a constant expression
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:117: error: static assertion failed
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:118: error: static assertion failed
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:119: error: static assertion failed
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:122: error: non-constant condition for static assertion
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:122:   in 'constexpr' expansion of 'totalOrder<__float128>(((__float128)((double)Test<__float128>::qnan)), ((__float128)(- Test<__float128>::inf)))'
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:88:   in 'constexpr' expansion of 'std::__cmp_alg::strong_order.std::__cmp_cust::_Strong_order::operator()<__float128&, __float128&>(e, f)'
.../libsupc++/compare:731:   in 'constexpr' expansion of 'std::__cmp_cust::__fp_strong_order<__float128>(__e, __f)'
.../testsuite/18_support/comparisons/algorithms/strong_order.cc:122: error: '(+QNaNf128 < -Inff128)' is not a constant expression

The same cases fail for long double on 32-bit x86, because that uses
the !is_iec559 overload as well, which needs a built-in as you said.

I think we still need a better implementation of the !is_iec559
overload for the __float128 case. Alternatively, if the built-in
handles all FP types (including non-IEEE ones) then we don't need a
second overload anyway.


[-- Attachment #2: patch.txt --]
[-- Type: text/x-patch, Size: 8699 bytes --]

diff --git a/libstdc++-v3/libsupc++/compare b/libstdc++-v3/libsupc++/compare
index 289145dea56..3698fbbfe72 100644
--- a/libstdc++-v3/libsupc++/compare
+++ b/libstdc++-v3/libsupc++/compare
@@ -37,6 +37,7 @@
 #pragma GCC visibility push(default)
 
 #include <concepts>
+#include <limits>
 
 namespace std
 {
@@ -578,23 +579,69 @@ namespace std
 
   namespace __cmp_cust
   {
-    template<floating_point _Tp>
-      constexpr weak_ordering
-      __fp_weak_ordering(_Tp __e, _Tp __f)
+    template<typename _Tp>
+      concept is_iec559 = floating_point<_Tp>
+	&& numeric_limits<_Tp>::is_iec559
+	// Exclude x86 12-byte long double:
+	&& ((sizeof(_Tp) & (sizeof(_Tp) - 1)) == 0);
+
+    template<floating_point _Fp> requires (!is_iec559<_Fp>)
+      constexpr strong_ordering
+      __fp_strong_order(_Fp __e, _Fp __f)
+      {
+	auto __pord = __e <=> __f;
+	if (is_lt(__pord))
+	  return strong_ordering::less;
+	else if (is_gt(__pord))
+	  return strong_ordering::greater;
+	else if (is_eq(__pord))
+	  return strong_ordering::equal;
+	else
+	  return strong_ordering::equivalent;
+      }
+
+    template<floating_point _Fp> requires is_iec559<_Fp>
+      constexpr strong_ordering
+      __fp_strong_order(_Fp __e, _Fp __f)
       {
-	// Returns an integer with the same sign as the argument, and magnitude
-	// indicating the classification: zero=1 subnorm=2 norm=3 inf=4 nan=5
-	auto __cat = [](_Tp __fp) -> int {
-	  const int __sign = __builtin_signbit(__fp) ? -1 : 1;
-	  if (__builtin_isnormal(__fp))
-	    return (__fp == 0 ? 1 : 3) * __sign;
-	  if (__builtin_isnan(__fp))
-	    return 5 * __sign;
-	  if (int __inf = __builtin_isinf_sign(__fp))
-	    return 4 * __inf;
-	  return 2 * __sign;
+	struct _Selector : __make_unsigned_selector_base
+	{
+	  using _Ints = _List<signed char, signed short, signed int,
+		signed long, signed long long
+#ifdef __GLIBCXX_TYPE_INT_N_0
+		  , signed __GLIBCXX_TYPE_INT_N_0
+#endif
+		  >;
+	  using type = typename __select<sizeof(_Fp), _Ints>::__type;
 	};
+	using _Si = _Selector::type;
+
+	auto __bit_cast = [](_Fp __x) -> _Si {
+#ifdef __clang__
+	    return __builtin_bit_cast(_Si, __x);
+#else
+	    using _Fp_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Fp;
+	    using _Si_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Si;
+	    return reinterpret_cast<_Si_v>(_Fp_v{__x})[0];
+#endif
+	};
+
+	constexpr int __unused_bits = sizeof(_Fp) <= 8
+	  ? 0
+	  : (sizeof(_Fp) - 8) * __CHAR_BIT__ - 1
+	    - __builtin_ctzll(_Si(__bit_cast(_Fp(-0.)) >> 8 * __CHAR_BIT__));
+	constexpr _Si __signbit = __bit_cast(_Fp(-0.)) << __unused_bits;
+	const _Si __ei = __bit_cast(__e) << __unused_bits;
+	const _Si __fi = __bit_cast(__f) << __unused_bits;
+	const _Si __e_cmp = __ei < 0 ? (~_Si(__ei ^ __signbit)) : __ei;
+	const _Si __f_cmp = __fi < 0 ? (~_Si(__fi ^ __signbit)) : __fi;
+	return __e_cmp <=> __f_cmp;
+      }
 
+    template<floating_point _Fp>
+      constexpr weak_ordering
+      __fp_weak_ordering(_Fp __e, _Fp __f)
+      {
 	auto __po = __e <=> __f;
 	if (is_lt(__po))
 	  return weak_ordering::less;
@@ -605,7 +652,7 @@ namespace std
 	else  // unordered, at least one argument is NaN
 	  {
 	    // return -1 for negative nan, +1 for positive nan, 0 otherwise.
-	    auto __isnan_sign = [](_Tp __fp) -> int {
+	    auto __isnan_sign = [](_Fp __fp) -> int {
 	      return __builtin_isnan(__fp)
 		? __builtin_signbit(__fp) ? -1 : 1
 		: 0;
@@ -650,7 +697,7 @@ namespace std
     template<typename _Tp, typename _Up>
       concept __strongly_ordered
 	= __adl_strong<_Tp, _Up>
-	  // FIXME: || floating_point<remove_reference_t<_Tp>>
+	  || floating_point<remove_reference_t<_Tp>>
 	  || __op_cmp<strong_ordering, _Tp, _Up>;
 
     class _Strong_order
@@ -680,10 +727,9 @@ namespace std
 	{
 	  static_assert(same_as<decay_t<_Tp>, decay_t<_Up>>);
 
-	  /* FIXME:
 	  if constexpr (floating_point<decay_t<_Tp>>)
 	    return __cmp_cust::__fp_strong_order(__e, __f);
-	  else */ if constexpr (__adl_strong<_Tp, _Up>)
+	  else if constexpr (__adl_strong<_Tp, _Up>)
 	    return strong_ordering(strong_order(static_cast<_Tp&&>(__e),
 						static_cast<_Up&&>(__f)));
 	  else if constexpr (__op_cmp<strong_ordering, _Tp, _Up>)
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc b/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc
index 2c813494ce7..a5ba751ac9f 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc
@@ -37,6 +37,33 @@ static_assert( different_cv_quals(42, 999) == strong_ordering::less );
 static_assert( different_cv_quals(-999, -999) == strong_ordering::equal );
 static_assert( different_cv_quals(-99, -111) == strong_ordering::greater );
 
+static_assert( strong_order(0.0, 0.0) == strong_ordering::equal );
+static_assert( strong_order(-0.0, -0.0) == strong_ordering::equal );
+static_assert( strong_order(-0.0, 0.0) == strong_ordering::less );
+static_assert( strong_order(0.0, -0.0) == strong_ordering::greater );
+
+constexpr double min = std::numeric_limits<double>::lowest();
+constexpr double max = std::numeric_limits<double>::max();
+constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+constexpr double inf = std::numeric_limits<double>::infinity();
+constexpr double denorm = std::numeric_limits<double>::denorm_min();
+constexpr double smallest = std::numeric_limits<double>::min();
+constexpr double epsilon = std::numeric_limits<double>::epsilon();
+static_assert( strong_order(denorm, smallest) == strong_ordering::less );
+static_assert( strong_order(denorm, 0.0) == strong_ordering::greater );
+static_assert( strong_order(0.0, nan) == strong_ordering::less );
+static_assert( strong_order(nan, nan) == strong_ordering::equal );
+static_assert( strong_order(nan, 0.0) == strong_ordering::greater );
+static_assert( strong_order(-nan, 0.0) == strong_ordering::less );
+static_assert( strong_order(-nan, min) == strong_ordering::less );
+static_assert( strong_order(-inf, min) == strong_ordering::less );
+static_assert( strong_order(-nan, -inf) == strong_ordering::less );
+static_assert( strong_order(-inf, -nan) == strong_ordering::greater );
+static_assert( strong_order(max, inf) == strong_ordering::less );
+static_assert( strong_order(inf, max) == strong_ordering::greater );
+static_assert( strong_order(inf, nan) == strong_ordering::less );
+static_assert( strong_order(1.0, 1.0+epsilon) == strong_ordering::less );
+
 namespace N
 {
   struct X { int i; };
@@ -54,3 +81,48 @@ static_assert( strong_order(X{1}, X{1}) == strong_ordering::equal );
 static_assert( strong_order(X{-1}, X{-2}) == strong_ordering::equivalent );
 static_assert( strong_order(X{1}, X{2}) == strong_ordering::greater );
 static_assert( !noexcept(strong_order(X{1}, X{2})) );
+
+template<typename T>
+constexpr bool totalOrder(T e, T f)
+{
+  return is_lteq(std::strong_order(e, f));
+}
+
+template <typename T> struct Test
+{
+  // numeric_limits<__float128> produces bogus values
+  static constexpr auto qnan = std::numeric_limits<double>::quiet_NaN();
+  static constexpr auto snan = std::numeric_limits<double>::signaling_NaN();
+  static constexpr auto inf  = std::numeric_limits<double>::infinity();
+
+  static_assert( totalOrder<T>( 0.0,  0.0));
+  static_assert( totalOrder<T>( 1.0,  1.0));
+  static_assert( totalOrder<T>(-1.0, -1.0));
+  static_assert( totalOrder<T>(-0.0, -0.0));
+  static_assert( totalOrder<T>(qnan, qnan));
+  static_assert( totalOrder<T>(snan, snan));
+  static_assert( totalOrder<T>( inf,  inf));
+  static_assert( totalOrder<T>(-0.0,  0.0));
+  static_assert( totalOrder<T>(-0.0, +1.0));
+  static_assert( totalOrder<T>(-qnan, +0.0));
+  static_assert( totalOrder<T>(-qnan, -inf));
+  static_assert( totalOrder<T>(-3., -2.));
+  static_assert( totalOrder<T>(2., 3.));
+  static_assert( totalOrder<T>(2., inf));
+  static_assert( totalOrder<T>(0., qnan));
+  static_assert( totalOrder<T>(0., 2.));
+  static_assert( totalOrder<T>(2., qnan));
+  static_assert( totalOrder<T>(inf, qnan));
+  static_assert( totalOrder<T>(snan, qnan));
+  static_assert(!totalOrder<T>(snan, -qnan));
+  static_assert(!totalOrder<T>(0.0, -0.0));
+  static_assert(!totalOrder<T>(0.0, -qnan));
+  static_assert(!totalOrder<T>(0.0, -1.0));
+  static_assert(!totalOrder<T>(1.0, -1.0));
+  static_assert(!totalOrder<T>(qnan, -inf));
+};
+
+Test<float> a;
+Test<double> b;
+Test<long double> c;
+Test<__float128> d;

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: IEC559 totalOrder
  2019-11-13 22:03 ` Jonathan Wakely
@ 2019-11-13 22:13   ` Jonathan Wakely
  0 siblings, 0 replies; 3+ messages in thread
From: Jonathan Wakely @ 2019-11-13 22:13 UTC (permalink / raw)
  To: Matthias Kretz; +Cc: libstdc++

[-- Attachment #1: Type: text/plain, Size: 7091 bytes --]

On 13/11/19 22:03 +0000, Jonathan Wakely wrote:
>On 09/11/19 08:40 +0100, Matthias Kretz wrote:
>>Here's an IEC559 totalOrder implementation that works with clang and GCC, for
>>float, double, long double, and __float128 (at least the non-clang branch
>>should work with float16, too):
>>https://godbolt.org/z/QzGWdB
>>
>>Note that I simplified the order reversal of negative values slightly, and
>>producing the bitmask for the sign is now "simpler".
>>
>>As Jakub noted on IRC, 32-bit x86's long double is a problem. Since it has
>>sizeof == 12 there's no *constexpr* way to cast it to an integer.
>>std::bit_cast also wouldn't help. It seems there's no way around a builtin for
>>this function. (Besides that, I ignored the presence of unnormal values in
>>long double - you still get a totalOrder, but I'm not sure IEC559 requests a
>>different behavior.)
>
>This is indistinguishable from magic, thanks!
>
>I've reworked your totalOrder as follows:
>
> template<floating_point _Fp> requires is_iec559<_Fp>
>   constexpr strong_ordering
>   __fp_strong_order(_Fp __e, _Fp __f)
>   {
>     struct _Selector : __make_unsigned_selector_base
>     {
>       using _Ints = _List<signed char, signed short, signed int,
>             signed long, signed long long
>#ifdef __GLIBCXX_TYPE_INT_N_0
>               , signed __GLIBCXX_TYPE_INT_N_0
>#endif
>               >;
>       using type = typename __select<sizeof(_Fp), _Ints>::__type;
>     };
>     using _Si = _Selector::type;
>
>     auto __bit_cast = [](_Fp __x) -> _Si {
>#ifdef __clang__
>         return __builtin_bit_cast(_Si, __x);
>#else
>         using _Fp_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Fp;
>         using _Si_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Si;
>         return reinterpret_cast<_Si_v>(_Fp_v{__x})[0];
>#endif
>     };
>
>     constexpr int __unused_bits = sizeof(_Fp) <= 8
>       ? 0
>       : (sizeof(_Fp) - 8) * __CHAR_BIT__ - 1
>         - __builtin_ctzll(_Si(__bit_cast(_Fp(-0.)) >> 8 * __CHAR_BIT__));
>     constexpr _Si __signbit = __bit_cast(_Fp(-0.)) << __unused_bits;
>     const _Si __ei = __bit_cast(__e) << __unused_bits;
>     const _Si __fi = __bit_cast(__f) << __unused_bits;
>     const _Si __e_cmp = __ei < 0 ? (~_Si(__ei ^ __signbit)) : __ei;
>     const _Si __f_cmp = __fi < 0 ? (~_Si(__fi ^ __signbit)) : __fi;
>     return __e_cmp <=> __f_cmp;
>   }
>
>And then for the testsuite we can define totalOrder as:
>
> template<typename T>
>   constexpr bool totalOrder(T e, T f)
>   {
>     return is_lteq(std::strong_order(e, f));
>   }
>
>I *think* I've got casts in the right places in __fp_strong_order to
>avoid problems with integer promotions when sizeof(_Fp) < sizeof(int),
>so that it's safe to immediately do [0] on the vector type to convert
>back to a scalar.
>
>Does that look OK? Have I broken it somehow?
>
>I've also defined this, for the non-IEEE 60559 case:
>
>   template<typename _Tp>
>     concept is_iec559 = floating_point<_Tp>
>	&& numeric_limits<_Tp>::is_iec559
>	// Exclude x86 12-byte long double:
>	&& ((sizeof(_Tp) & (sizeof(_Tp) - 1)) == 0);
>
>   template<floating_point _Fp> requires (!is_iec559<_Fp>)
>     constexpr strong_ordering
>     __fp_strong_order(_Fp __e, _Fp __f)
>     {
>	auto __pord = __e <=> __f;
>	if (is_lt(__pord))
>	  return strong_ordering::less;
>	else if (is_gt(__pord))
>	  return strong_ordering::greater;
>	else if (is_eq(__pord))
>	  return strong_ordering::equal;
>	else
>	  return strong_ordering::equivalent;
>     }
>
>A patch against current trunk is attached, although it fails some
>__float128 tests, because is_iec559 is false for that type:
>
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc: In instantiation of 'struct Test<__float128>':
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:128:   required from here
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:107: error: non-constant condition for static assertion
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:107:   in 'constexpr' expansion of 'totalOrder<__float128>(((__float128)(- Test<__float128>::qnan)), ((__float128)(+0.0)))'
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:88:   in 'constexpr' expansion of 'std::__cmp_alg::strong_order.std::__cmp_cust::_Strong_order::operator()<__float128&, __float128&>(e, f)'
>.../libsupc++/compare:731:   in 'constexpr' expansion of 'std::__cmp_cust::__fp_strong_order<__float128>(__e, __f)'
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:107: error: '(-QNaNf128 < 0.0f128)' is not a constant expression
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:108: error: non-constant condition for static assertion
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:108:   in 'constexpr' expansion of 'totalOrder<__float128>(((__float128)(- Test<__float128>::qnan)), ((__float128)(- Test<__float128>::inf)))'
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:88:   in 'constexpr' expansion of 'std::__cmp_alg::strong_order.std::__cmp_cust::_Strong_order::operator()<__float128&, __float128&>(e, f)'
>.../libsupc++/compare:731:   in 'constexpr' expansion of 'std::__cmp_cust::__fp_strong_order<__float128>(__e, __f)'
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:108: error: '(-QNaNf128 < -Inff128)' is not a constant expression
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:117: error: static assertion failed
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:118: error: static assertion failed
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:119: error: static assertion failed
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:122: error: non-constant condition for static assertion
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:122:   in 'constexpr' expansion of 'totalOrder<__float128>(((__float128)((double)Test<__float128>::qnan)), ((__float128)(- Test<__float128>::inf)))'
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:88:   in 'constexpr' expansion of 'std::__cmp_alg::strong_order.std::__cmp_cust::_Strong_order::operator()<__float128&, __float128&>(e, f)'
>.../libsupc++/compare:731:   in 'constexpr' expansion of 'std::__cmp_cust::__fp_strong_order<__float128>(__e, __f)'
>.../testsuite/18_support/comparisons/algorithms/strong_order.cc:122: error: '(+QNaNf128 < -Inff128)' is not a constant expression
>
>The same cases fail for long double on 32-bit x86, because that uses
>the !is_iec559 overload as well, which needs a built-in as you said.
>
>I think we still need a better implementation of the !is_iec559
>overload for the __float128 case. Alternatively, if the built-in
>handles all FP types (including non-IEEE ones) then we don't need a
>second overload anyway.

Here's another version of that patch that doesn't include unrelated
edits to __fp_weak_order, so is a bit easier to read.



[-- Attachment #2: patch.txt --]
[-- Type: text/x-patch, Size: 8016 bytes --]

diff --git a/libstdc++-v3/libsupc++/compare b/libstdc++-v3/libsupc++/compare
index 289145dea56..5b68392d85d 100644
--- a/libstdc++-v3/libsupc++/compare
+++ b/libstdc++-v3/libsupc++/compare
@@ -37,6 +37,7 @@
 #pragma GCC visibility push(default)
 
 #include <concepts>
+#include <limits>
 
 namespace std
 {
@@ -578,6 +579,65 @@ namespace std
 
   namespace __cmp_cust
   {
+    template<typename _Tp>
+      concept is_iec559 = floating_point<_Tp>
+	&& numeric_limits<_Tp>::is_iec559
+	// Exclude x86 12-byte long double:
+	&& ((sizeof(_Tp) & (sizeof(_Tp) - 1)) == 0);
+
+    template<floating_point _Fp> requires (!is_iec559<_Fp>)
+      constexpr strong_ordering
+      __fp_strong_order(_Fp __e, _Fp __f)
+      {
+	auto __pord = __e <=> __f;
+	if (is_lt(__pord))
+	  return strong_ordering::less;
+	else if (is_gt(__pord))
+	  return strong_ordering::greater;
+	else if (is_eq(__pord))
+	  return strong_ordering::equal;
+	else
+	  return strong_ordering::equivalent;
+      }
+
+    template<floating_point _Fp> requires is_iec559<_Fp>
+      constexpr strong_ordering
+      __fp_strong_order(_Fp __e, _Fp __f)
+      {
+	struct _Selector : __make_unsigned_selector_base
+	{
+	  using _Ints = _List<signed char, signed short, signed int,
+		signed long, signed long long
+#ifdef __GLIBCXX_TYPE_INT_N_0
+		  , signed __GLIBCXX_TYPE_INT_N_0
+#endif
+		  >;
+	  using type = typename __select<sizeof(_Fp), _Ints>::__type;
+	};
+	using _Si = _Selector::type;
+
+	auto __bit_cast = [](_Fp __x) -> _Si {
+#ifdef __clang__
+	    return __builtin_bit_cast(_Si, __x);
+#else
+	    using _Fp_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Fp;
+	    using _Si_v [[__gnu__::__vector_size__(sizeof(_Fp))]] = _Si;
+	    return reinterpret_cast<_Si_v>(_Fp_v{__x})[0];
+#endif
+	};
+
+	constexpr int __unused_bits = sizeof(_Fp) <= 8
+	  ? 0
+	  : (sizeof(_Fp) - 8) * __CHAR_BIT__ - 1
+	    - __builtin_ctzll(_Si(__bit_cast(_Fp(-0.)) >> 8 * __CHAR_BIT__));
+	constexpr _Si __signbit = __bit_cast(_Fp(-0.)) << __unused_bits;
+	const _Si __ei = __bit_cast(__e) << __unused_bits;
+	const _Si __fi = __bit_cast(__f) << __unused_bits;
+	const _Si __e_cmp = __ei < 0 ? (~_Si(__ei ^ __signbit)) : __ei;
+	const _Si __f_cmp = __fi < 0 ? (~_Si(__fi ^ __signbit)) : __fi;
+	return __e_cmp <=> __f_cmp;
+      }
+
     template<floating_point _Tp>
       constexpr weak_ordering
       __fp_weak_ordering(_Tp __e, _Tp __f)
@@ -605,7 +665,7 @@ namespace std
 	else  // unordered, at least one argument is NaN
 	  {
 	    // return -1 for negative nan, +1 for positive nan, 0 otherwise.
-	    auto __isnan_sign = [](_Tp __fp) -> int {
+	    auto __isnan_sign = [](_Fp __fp) -> int {
 	      return __builtin_isnan(__fp)
 		? __builtin_signbit(__fp) ? -1 : 1
 		: 0;
@@ -650,7 +710,7 @@ namespace std
     template<typename _Tp, typename _Up>
       concept __strongly_ordered
 	= __adl_strong<_Tp, _Up>
-	  // FIXME: || floating_point<remove_reference_t<_Tp>>
+	  || floating_point<remove_reference_t<_Tp>>
 	  || __op_cmp<strong_ordering, _Tp, _Up>;
 
     class _Strong_order
@@ -680,10 +740,9 @@ namespace std
 	{
 	  static_assert(same_as<decay_t<_Tp>, decay_t<_Up>>);
 
-	  /* FIXME:
 	  if constexpr (floating_point<decay_t<_Tp>>)
 	    return __cmp_cust::__fp_strong_order(__e, __f);
-	  else */ if constexpr (__adl_strong<_Tp, _Up>)
+	  else if constexpr (__adl_strong<_Tp, _Up>)
 	    return strong_ordering(strong_order(static_cast<_Tp&&>(__e),
 						static_cast<_Up&&>(__f)));
 	  else if constexpr (__op_cmp<strong_ordering, _Tp, _Up>)
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc b/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc
index 2c813494ce7..a5ba751ac9f 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/algorithms/strong_order.cc
@@ -37,6 +37,33 @@ static_assert( different_cv_quals(42, 999) == strong_ordering::less );
 static_assert( different_cv_quals(-999, -999) == strong_ordering::equal );
 static_assert( different_cv_quals(-99, -111) == strong_ordering::greater );
 
+static_assert( strong_order(0.0, 0.0) == strong_ordering::equal );
+static_assert( strong_order(-0.0, -0.0) == strong_ordering::equal );
+static_assert( strong_order(-0.0, 0.0) == strong_ordering::less );
+static_assert( strong_order(0.0, -0.0) == strong_ordering::greater );
+
+constexpr double min = std::numeric_limits<double>::lowest();
+constexpr double max = std::numeric_limits<double>::max();
+constexpr double nan = std::numeric_limits<double>::quiet_NaN();
+constexpr double inf = std::numeric_limits<double>::infinity();
+constexpr double denorm = std::numeric_limits<double>::denorm_min();
+constexpr double smallest = std::numeric_limits<double>::min();
+constexpr double epsilon = std::numeric_limits<double>::epsilon();
+static_assert( strong_order(denorm, smallest) == strong_ordering::less );
+static_assert( strong_order(denorm, 0.0) == strong_ordering::greater );
+static_assert( strong_order(0.0, nan) == strong_ordering::less );
+static_assert( strong_order(nan, nan) == strong_ordering::equal );
+static_assert( strong_order(nan, 0.0) == strong_ordering::greater );
+static_assert( strong_order(-nan, 0.0) == strong_ordering::less );
+static_assert( strong_order(-nan, min) == strong_ordering::less );
+static_assert( strong_order(-inf, min) == strong_ordering::less );
+static_assert( strong_order(-nan, -inf) == strong_ordering::less );
+static_assert( strong_order(-inf, -nan) == strong_ordering::greater );
+static_assert( strong_order(max, inf) == strong_ordering::less );
+static_assert( strong_order(inf, max) == strong_ordering::greater );
+static_assert( strong_order(inf, nan) == strong_ordering::less );
+static_assert( strong_order(1.0, 1.0+epsilon) == strong_ordering::less );
+
 namespace N
 {
   struct X { int i; };
@@ -54,3 +81,48 @@ static_assert( strong_order(X{1}, X{1}) == strong_ordering::equal );
 static_assert( strong_order(X{-1}, X{-2}) == strong_ordering::equivalent );
 static_assert( strong_order(X{1}, X{2}) == strong_ordering::greater );
 static_assert( !noexcept(strong_order(X{1}, X{2})) );
+
+template<typename T>
+constexpr bool totalOrder(T e, T f)
+{
+  return is_lteq(std::strong_order(e, f));
+}
+
+template <typename T> struct Test
+{
+  // numeric_limits<__float128> produces bogus values
+  static constexpr auto qnan = std::numeric_limits<double>::quiet_NaN();
+  static constexpr auto snan = std::numeric_limits<double>::signaling_NaN();
+  static constexpr auto inf  = std::numeric_limits<double>::infinity();
+
+  static_assert( totalOrder<T>( 0.0,  0.0));
+  static_assert( totalOrder<T>( 1.0,  1.0));
+  static_assert( totalOrder<T>(-1.0, -1.0));
+  static_assert( totalOrder<T>(-0.0, -0.0));
+  static_assert( totalOrder<T>(qnan, qnan));
+  static_assert( totalOrder<T>(snan, snan));
+  static_assert( totalOrder<T>( inf,  inf));
+  static_assert( totalOrder<T>(-0.0,  0.0));
+  static_assert( totalOrder<T>(-0.0, +1.0));
+  static_assert( totalOrder<T>(-qnan, +0.0));
+  static_assert( totalOrder<T>(-qnan, -inf));
+  static_assert( totalOrder<T>(-3., -2.));
+  static_assert( totalOrder<T>(2., 3.));
+  static_assert( totalOrder<T>(2., inf));
+  static_assert( totalOrder<T>(0., qnan));
+  static_assert( totalOrder<T>(0., 2.));
+  static_assert( totalOrder<T>(2., qnan));
+  static_assert( totalOrder<T>(inf, qnan));
+  static_assert( totalOrder<T>(snan, qnan));
+  static_assert(!totalOrder<T>(snan, -qnan));
+  static_assert(!totalOrder<T>(0.0, -0.0));
+  static_assert(!totalOrder<T>(0.0, -qnan));
+  static_assert(!totalOrder<T>(0.0, -1.0));
+  static_assert(!totalOrder<T>(1.0, -1.0));
+  static_assert(!totalOrder<T>(qnan, -inf));
+};
+
+Test<float> a;
+Test<double> b;
+Test<long double> c;
+Test<__float128> d;

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2019-11-13 22:13 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-09  7:40 IEC559 totalOrder Matthias Kretz
2019-11-13 22:03 ` Jonathan Wakely
2019-11-13 22:13   ` Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).