IEC559 totalOrder
Jonathan Wakely
jwakely@redhat.com
Wed Nov 13 22:03:00 GMT 2019
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.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: patch.txt
Type: text/x-patch
Size: 8486 bytes
Desc: not available
URL: <http://gcc.gnu.org/pipermail/libstdc++/attachments/20191113/d33c3ac2/attachment.bin>
More information about the Libstdc++
mailing list