public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
@ 2021-05-04 12:31 ` rguenth at gcc dot gnu.org
  2024-02-29 18:23 ` g.peterhoff@t-online.de
                   ` (18 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: rguenth at gcc dot gnu.org @ 2021-05-04 12:31 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

Richard Biener <rguenth at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |ASSIGNED

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
  2021-05-04 12:31 ` [Bug libstdc++/77776] C++17 std::hypot implementation is poor rguenth at gcc dot gnu.org
@ 2024-02-29 18:23 ` g.peterhoff@t-online.de
  2024-03-01 12:27 ` redi at gcc dot gnu.org
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-02-29 18:23 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

g.peterhoff@t-online.de changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |g.peterhoff@t-online.de

--- Comment #11 from g.peterhoff@t-online.de ---
Would this be a good implementation for hypot3 in cmath?

#define GCC_UNLIKELY(x) __builtin_expect(x, 0)
#define GCC_LIKELY(x) __builtin_expect(x, 1)

namespace __detail
{
        template <typename _Tp>
        inline _GLIBCXX_CONSTEXPR typename
enable_if<is_floating_point<_Tp>::value, bool>::type __isinf3(const _Tp __x,
const _Tp __y, const _Tp __z)   noexcept
        {
                return bool(int(std::isinf(__x)) | int(std::isinf(__y)) |
int(std::isinf(__z)));
        }

        template <typename _Tp>
        inline _GLIBCXX_CONSTEXPR typename
enable_if<is_floating_point<_Tp>::value, _Tp>::type  __hypot3(_Tp __x, _Tp __y,
_Tp __z)     noexcept
        {
                __x = std::fabs(__x);
                __y = std::fabs(__y);
                __z = std::fabs(__z);

                const _Tp
                        __max = std::fmax(std::fmax(__x, __y), __z);

                if (GCC_UNLIKELY(__max == _Tp{}))
                {
                        return __max;
                }
                else
                {
                        __x /= __max;
                        __y /= __max;
                        __z /= __max;
                        return std::sqrt(__x*__x + __y*__y + __z*__z) * __max;
                }
        }
}       //      __detail


        template <typename _Tp>
        inline _GLIBCXX_CONSTEXPR typename
enable_if<is_floating_point<_Tp>::value, _Tp>::type  __hypot3(const _Tp __x,
const _Tp __y, const _Tp __z)   noexcept
        {
                return (GCC_UNLIKELY(__detail::__isinf3(__x, __y, __z))) ?
numeric_limits<_Tp>::infinity() : __detail::__hypot3(__x, __y, __z);
        }

#undef GCC_UNLIKELY
#undef GCC_LIKELY

How does it work?
* Basically, I first pull out the special case INFINITY (see
https://en.cppreference.com/w/cpp/numeric/math/hypot).
* As an additional safety measure (to prevent misuse) the functions are defined
by enable_if.

constexpr
* The hypot3 functions can thus be defined as _GLIBCXX_CONSTEXPR.

Questions
* To get a better runtime behavior I define GCC_(UN)LIKELY. Are there already
such macros (which I have overlooked)?
* The functions are noexcept. Does that make sense? If yes: why are the math
functions not noexcept?

thx
Gero

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
  2021-05-04 12:31 ` [Bug libstdc++/77776] C++17 std::hypot implementation is poor rguenth at gcc dot gnu.org
  2024-02-29 18:23 ` g.peterhoff@t-online.de
@ 2024-03-01 12:27 ` redi at gcc dot gnu.org
  2024-03-02 15:52 ` g.peterhoff@t-online.de
                   ` (16 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: redi at gcc dot gnu.org @ 2024-03-01 12:27 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #12 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to g.peterhoff from comment #11)
> Would this be a good implementation for hypot3 in cmath?

Thanks for the suggestion!

> #define GCC_UNLIKELY(x) __builtin_expect(x, 0)
> #define GCC_LIKELY(x) __builtin_expect(x, 1)
> 
> namespace __detail
> {
> 	template <typename _Tp>
> 	inline _GLIBCXX_CONSTEXPR typename enable_if<is_floating_point<_Tp>::value,
> bool>::type	__isinf3(const _Tp __x, const _Tp __y, const _Tp __z)	noexcept
> 	{
> 		return bool(int(std::isinf(__x)) | int(std::isinf(__y)) |
> int(std::isinf(__z)));

The casts are redundant and just make it harder to read IMHO:

  return std::isinf(__x) | std::isinf(__y) | std::isinf(__z);



> 	}
> 
> 	template <typename _Tp>
> 	inline _GLIBCXX_CONSTEXPR typename enable_if<is_floating_point<_Tp>::value,
> _Tp>::type	__hypot3(_Tp __x, _Tp __y, _Tp __z)	noexcept
> 	{
> 		__x = std::fabs(__x);
> 		__y = std::fabs(__y);
> 		__z = std::fabs(__z);
> 
> 		const _Tp
> 			__max = std::fmax(std::fmax(__x, __y), __z);
> 
> 		if (GCC_UNLIKELY(__max == _Tp{}))
> 		{
> 			return __max;
> 		}
> 		else
> 		{
> 			__x /= __max;
> 			__y /= __max;
> 			__z /= __max;
> 			return std::sqrt(__x*__x + __y*__y + __z*__z) * __max;
> 		}
> 	}
> }	//	__detail
> 
> 
> 	template <typename _Tp>
> 	inline _GLIBCXX_CONSTEXPR typename enable_if<is_floating_point<_Tp>::value,
> _Tp>::type	__hypot3(const _Tp __x, const _Tp __y, const _Tp __z)	noexcept

This is a C++17 function, you can use enable_if_t<is_floating_point_v<_Tp>>,
but see below.

> 	{
> 		return (GCC_UNLIKELY(__detail::__isinf3(__x, __y, __z))) ?
> numeric_limits<_Tp>::infinity() : __detail::__hypot3(__x, __y, __z);
> 	}
> 
> #undef GCC_UNLIKELY
> #undef GCC_LIKELY
> 
> How does it work?
> * Basically, I first pull out the special case INFINITY (see
> https://en.cppreference.com/w/cpp/numeric/math/hypot).
> * As an additional safety measure (to prevent misuse) the functions are
> defined by enable_if.

I don't think we want to slow down compilation like that. If users decide to
misuse std::__detail::__isinf3 then they get what they deserve.


> constexpr
> * The hypot3 functions can thus be defined as _GLIBCXX_CONSTEXPR.

Just use 'constexpr' because this function isn't compiled as C++98. Then you
don't need the 'inline'. Although the standard doesn't allow std::hypot3 to be
constexpr.

> Questions
> * To get a better runtime behavior I define GCC_(UN)LIKELY. Are there
> already such macros (which I have overlooked)?

No, but you can do:


  if (__isinf3(__x, __y, __x)) [[__unlikely__]]
    ...


> * The functions are noexcept. Does that make sense? If yes: why are the math
> functions not noexcept?

I think it's just because nobody bothered to add them, and I doubt much code
ever needs to check whether they are noexcept. The compiler should already know
the standard libm functions don't throw. For this function (which isn't in libm
and the compiler doesn't know about) it seems worth adding 'noexcept'.


Does splitting it into three functions matter? It seems simpler as a single
function:

  template<typename _Tp>
    constexpr _Tp
    __hypot3(_Tp __x, _Tp __y, _Tp __z) noexcept
    {
      if (std::isinf(__x) | std::isinf(__y) | std::isinf(__z)) [[__unlikely__]]
        return numeric_limits<_Tp>::infinity();
      __x = std::fabs(__x);
      __y = std::fabs(__y);
      __z = std::fabs(__z);
      const _Tp __max = std::fmax(std::fmax(__x, __y), __z);
      if (__max == _Tp{}) [[__unlikely__]]
        return __max;
      else
        {
          __x /= __max;
          __y /= __max;
          __z /= __max;
          return std::sqrt(__x*__x + __y*__y + __z*__z) * __max;
        }
    }

This would add a dependency on <limits> to <cmath>, which isn't currently
there. Maybe we could just return (_Tp)__builtin_huge_vall().

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (2 preceding siblings ...)
  2024-03-01 12:27 ` redi at gcc dot gnu.org
@ 2024-03-02 15:52 ` g.peterhoff@t-online.de
  2024-03-04  3:49 ` de34 at live dot cn
                   ` (15 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-03-02 15:52 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #13 from g.peterhoff@t-online.de ---
Thanks for the suggestions:
template <typename _Tp>
constexpr _Tp __hypot3(_Tp __x, _Tp __y, _Tp __z)       noexcept
{
        if (std::isinf(__x) | std::isinf(__y) | std::isinf(__z))
[[__unlikely__]]
                return _Tp(INFINITY);
        __x = std::fabs(__x);
        __y = std::fabs(__y);
        __z = std::fabs(__z);
        const _Tp __max = std::fmax(std::fmax(__x, __y), __z);
        if (__max == _Tp{}) [[__unlikely__]]
                return __max;
        __x /= __max;
        __y /= __max;
        __z /= __max;
        return std::sqrt(__x*__x + __y*__y + __z*__z) * __max;
}

The functions are then set to constexpr/noexcept.

regards
Gero

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (3 preceding siblings ...)
  2024-03-02 15:52 ` g.peterhoff@t-online.de
@ 2024-03-04  3:49 ` de34 at live dot cn
  2024-03-04 10:45 ` mkretz at gcc dot gnu.org
                   ` (14 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: de34 at live dot cn @ 2024-03-04  3:49 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

Jiang An <de34 at live dot cn> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |de34 at live dot cn

--- Comment #14 from Jiang An <de34 at live dot cn> ---
(In reply to g.peterhoff from comment #11)

> constexpr
> * The hypot3 functions can thus be defined as _GLIBCXX_CONSTEXPR.

I think it should be marked _GLIBCXX26_CONSTEXPR.
(and we may also need to change bits/c++config)

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (4 preceding siblings ...)
  2024-03-04  3:49 ` de34 at live dot cn
@ 2024-03-04 10:45 ` mkretz at gcc dot gnu.org
  2024-03-04 11:12 ` jakub at gcc dot gnu.org
                   ` (13 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: mkretz at gcc dot gnu.org @ 2024-03-04 10:45 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #15 from Matthias Kretz (Vir) <mkretz at gcc dot gnu.org> ---
Your implementation still needs to solve:

1. Loss of precision because of division & subsequent scaling by max. Users
comparing std::hypot(x, y, z) against a simple std::sqrt(x * x + y * y + z * z)
might wonder why they want to use std::hypot if it's less precise.

2. Relatively high cost (in latency and throughput) because of the three
divisions. You could replace it with scale = 1/max; x *= scale; ... But that
can reduce precision even further.

3. Summation of the x, y, and z squares isn't associative if you care about
precision. A high quality implementation needs to add the two lowest values
first.

Here's a precise but inefficient implementation:
(https://compiler-explorer.com/z/ocGPnsYE3)

template <typename T>
[[gnu::optimize("-fno-unsafe-math-optimizations")]]
T
hypot3(T x, T y, T z)
{
  x = std::abs(x);
  y = std::abs(y);
  z = std::abs(z);
  if (std::isinf(x) || std::isinf(y) || std::isinf(z))
    return std::__infinity_v<T>;
  else if (std::isnan(x) || std::isnan(y) || std::isnan(z))
    return std::__quiet_NaN_v<T>;
  else if (x == y && y == z)
    return x * std::sqrt(T(3));
  else if (z == 0 && y == 0)
    return x;
  else if (x == 0 && z == 0)
    return y;
  else if (x == 0 && y == 0)
    return z;
  else
    {
      T hi = std::max(std::max(x, y), z);
      T lo0 = std::min(std::max(x, y), z);
      T lo1 = std::min(x, y);
      int e = 0;
      hi = std::frexp(hi, &e);
      lo0 = std::ldexp(lo0, -e);
      lo1 = std::ldexp(lo1, -e);
      T lo = lo0 * lo0 + lo1 * lo1;
      return std::ldexp(std::sqrt(hi * hi + lo), e);
    }
}

AFAIK
https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/experimental/bits/simd_math.h;h=06e7b4496f9917f886f66fbd7629700dd17e55f9;hb=HEAD#l1168
is a precise and efficient implementation. It also avoids division altogether
unless an input is subnormal.

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (5 preceding siblings ...)
  2024-03-04 10:45 ` mkretz at gcc dot gnu.org
@ 2024-03-04 11:12 ` jakub at gcc dot gnu.org
  2024-03-04 17:14 ` mkretz at gcc dot gnu.org
                   ` (12 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: jakub at gcc dot gnu.org @ 2024-03-04 11:12 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

Jakub Jelinek <jakub at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |jakub at gcc dot gnu.org

--- Comment #16 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
(In reply to Matthias Kretz (Vir) from comment #15)
> Your implementation still needs to solve:
> 
> 1. Loss of precision because of division & subsequent scaling by max. Users
> comparing std::hypot(x, y, z) against a simple std::sqrt(x * x + y * y + z *
> z) might wonder why they want to use std::hypot if it's less precise.
> 
> 2. Relatively high cost (in latency and throughput) because of the three
> divisions. You could replace it with scale = 1/max; x *= scale; ... But that
> can reduce precision even further.
> 
> 3. Summation of the x, y, and z squares isn't associative if you care about
> precision. A high quality implementation needs to add the two lowest values
> first.
> 
> Here's a precise but inefficient implementation:
> (https://compiler-explorer.com/z/ocGPnsYE3)
> 
> template <typename T>
> [[gnu::optimize("-fno-unsafe-math-optimizations")]]
> T
> hypot3(T x, T y, T z)
> {
>   x = std::abs(x);
>   y = std::abs(y);
>   z = std::abs(z);
>   if (std::isinf(x) || std::isinf(y) || std::isinf(z))
>     return std::__infinity_v<T>;
>   else if (std::isnan(x) || std::isnan(y) || std::isnan(z))
>     return std::__quiet_NaN_v<T>;
>   else if (x == y && y == z)
>     return x * std::sqrt(T(3));
>   else if (z == 0 && y == 0)
>     return x;
>   else if (x == 0 && z == 0)
>     return y;
>   else if (x == 0 && y == 0)
>     return z;
>   else
>     {
>       T hi = std::max(std::max(x, y), z);
>       T lo0 = std::min(std::max(x, y), z);
>       T lo1 = std::min(x, y);
>       int e = 0;
>       hi = std::frexp(hi, &e);
>       lo0 = std::ldexp(lo0, -e);
>       lo1 = std::ldexp(lo1, -e);
>       T lo = lo0 * lo0 + lo1 * lo1;
>       return std::ldexp(std::sqrt(hi * hi + lo), e);
>     }
> }
> 
> AFAIK
> https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/
> experimental/bits/simd_math.h;h=06e7b4496f9917f886f66fbd7629700dd17e55f9;
> hb=HEAD#l1168 is a precise and efficient implementation. It also avoids
> division altogether unless an input is subnormal.

What glibc does there for the 2 argument hypot is after handling the non-finite
cases finds the minimum and maximum and uses just normal multiplication,
addition + sqrt for the common case where maximum isn't too large and minimum
isn't too small.
So, no need to use frexp/ldexp, just comparisons of hi above against sqrt of
(max finite / 3), in that case scale by multiplying all 3 args by some
appropriate scale constant, and similarly otherwise if lo1 is too small by some
large scale.

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (6 preceding siblings ...)
  2024-03-04 11:12 ` jakub at gcc dot gnu.org
@ 2024-03-04 17:14 ` mkretz at gcc dot gnu.org
  2024-03-04 20:21 ` jakub at gcc dot gnu.org
                   ` (11 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: mkretz at gcc dot gnu.org @ 2024-03-04 17:14 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #17 from Matthias Kretz (Vir) <mkretz at gcc dot gnu.org> ---
hypotf(a, b) is implemented using double precision and hypot(a, b) uses 80-bit
long double on i386 and x86_64 hypot does what you describe, right?

std::experimental::simd benchmarks of hypot(a, b), where simd_abi::scalar uses
the <cmath> implementation (i.e. glibc):


-march=skylake-avx512 -ffast-math -O3 -lmvec:
              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
 float, simd_abi::scalar                   37.5           1           11.5     
     1
 float,                                    37.6       0.999           10.2     
  1.13
 float, simd_abi::__sse                      34        4.42           6.46     
  7.15
 float, simd_abi::__avx                    34.1        8.79           6.56     
  14.1
 float, simd_abi::_Avx512<32>              34.3        8.76           6.01     
  15.4
 float, simd_abi::_Avx512<64>              44.1        13.6             12     
  15.4
 float, [[gnu::vector_size(16)]]           58.3        2.57           47.5     
 0.974
 float, [[gnu::vector_size(32)]]            132        2.27            104     
 0.892
 float, [[gnu::vector_size(64)]]            240         2.5            222     
 0.832
--------------------------------------------------------------------------------------
              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
double, simd_abi::scalar                     81           1           21.5     
     1
double,                                    80.1        1.01           21.3     
  1.01
double, simd_abi::__sse                    39.9        4.06           6.47     
  6.64
double, simd_abi::__avx                    40.2        8.05             12     
  7.14
double, simd_abi::_Avx512<32>              40.3        8.04             12     
  7.14
double, simd_abi::_Avx512<64>              56.2        11.5             24     
  7.14
double, [[gnu::vector_size(16)]]           89.3        1.81           42.5     
  1.01
double, [[gnu::vector_size(32)]]            150        2.16            110     
 0.777
double, [[gnu::vector_size(64)]]            297        2.18            242     
  0.71
--------------------------------------------------------------------------------------

-march=skylake-avx512 -O3 -lmvec:
              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
 float, simd_abi::scalar                   37.6           1           10.4     
     1
 float,                                    37.7       0.998           10.2     
  1.02                                                                          
 float, simd_abi::__sse                    37.6           4           8.83     
  4.71                                                                          
 float, simd_abi::__avx                    37.5        8.01           9.42     
  8.82
 float, simd_abi::_Avx512<64>              47.8        12.6             12     
  13.8
 float, [[gnu::vector_size(16)]]           98.7        1.52           57.2     
 0.727
 float, [[gnu::vector_size(32)]]            151           2            114     
 0.728
 float, [[gnu::vector_size(64)]]            260        2.31            230     
 0.722
--------------------------------------------------------------------------------------
              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
double, simd_abi::scalar                   79.7           1           21.7     
     1
double,                                    80.1       0.995           21.6     
     1
double, simd_abi::__sse                    44.2         3.6           9.99     
  4.33
double, simd_abi::__avx                    43.6        7.32             12     
  7.21
double, simd_abi::_Avx512<64>              59.9        10.6             24     
  7.21
double, [[gnu::vector_size(16)]]           88.3         1.8           44.2     
  0.98
double, [[gnu::vector_size(32)]]            163        1.96            115     
  0.75
double, [[gnu::vector_size(64)]]            302        2.11            233     
 0.742
--------------------------------------------------------------------------------------

I have never ported my SIMD implementation back to scalar and benchmarked it
against glibc.

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (7 preceding siblings ...)
  2024-03-04 17:14 ` mkretz at gcc dot gnu.org
@ 2024-03-04 20:21 ` jakub at gcc dot gnu.org
  2024-03-06  2:47 ` g.peterhoff@t-online.de
                   ` (10 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: jakub at gcc dot gnu.org @ 2024-03-04 20:21 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #18 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
I was looking at the sysdeps/ieee754/ldbl-128/ version, i.e. what is used for
hypotf128.

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (8 preceding siblings ...)
  2024-03-04 20:21 ` jakub at gcc dot gnu.org
@ 2024-03-06  2:47 ` g.peterhoff@t-online.de
  2024-03-06  9:43 ` mkretz at gcc dot gnu.org
                   ` (9 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-03-06  2:47 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #19 from g.peterhoff@t-online.de ---
> So, no need to use frexp/ldexp, just comparisons of hi above against sqrt of
> (max finite / 3), in that case scale by multiplying all 3 args by some
> appropriate scale constant, and similarly otherwise if lo1 is too small by
> some large scale.

I don't really know. With frexp/ldexp you probably get the highest accuracy
(even if it is probably slower) instead of doing it manually. The problem is to
determine suitable scaling factors and to adjust the (return)values
accordingly. I have implemented both cases.

Error
* In the case (x==y && y==z), x*std::sqrt(T(3)) must not simply be returned, as
this can lead to an overflow (inf).

Generally
* Instead of using fmin/fmax to determine the values hi,lo1,lo0, it is better
to sort x,y,z. This is faster and clearer and no additional variables need to
be introduced.
* It also makes sense to consider the case (x==0 && y==0 && z==0).

Optimizations
* You were probably wondering why I wrote "if (std::isinf(x) | std::isinf(y) |
std::isinf(z))", for example. This is intentional. The problem is that gcc
almost always produces branch code for logical operations, so *a lot* of
conditional jumps. By using arithmetic operations, so instead of || && just |
&, I can get it to generate only actually necessary conditional jumps or
cmoves. branchfree code is always better.


template <typename T>
constexpr T     hypot3_exp(T x, T y, T z) noexcept
{
        using limits = std::numeric_limits<T>;

        constexpr T
                zero = 0;

        x = std::abs(x);
        y = std::abs(y);
        z = std::abs(z);

        if (std::isinf(x) | std::isinf(y) | std::isinf(z))  [[unlikely]]
                return limits::infinity();
        if (std::isnan(x) | std::isnan(y) | std::isnan(z))      [[unlikely]]
                return limits::quiet_NaN();
        if ((x==zero) & (y==zero) & (z==zero))  [[unlikely]]
                return zero;
        if ((y==zero) & (z==zero))      [[unlikely]]
                return x;
        if ((x==zero) & (z==zero))      [[unlikely]]
                return y;
        if ((x==zero) & (y==zero))      [[unlikely]]
                return z;

        auto sort = [](T& a, T& b, T& c)        constexpr noexcept -> void
        {
                if (a > b) std::swap(a, b);
                if (b > c) std::swap(b, c);
                if (a > b) std::swap(a, b);
        };

        sort(x, y, z);  //      x <= y <= z

        int
                exp = 0;

        z = std::frexp(z, &exp);
        y = std::ldexp(y, -exp);
        x = std::ldexp(x, -exp);

        T
                sum = x*x + y*y;

        sum += z*z;
        return std::ldexp(std::sqrt(sum), exp);
}

template <typename T>
constexpr T     hypot3_scale(T x, T y, T z) noexcept
{
        using limits = std::numeric_limits<T>;

        auto prev_power2 = [](const T value)    constexpr noexcept -> T
        {
                return std::exp2(std::floor(std::log2(value)));
        };

        constexpr T
                sqrtmax         = std::sqrt(limits::max()),
                scale_up        = prev_power2(sqrtmax),
                scale_down      = T(1) / scale_up,
                zero            = 0;

        x = std::abs(x);
        y = std::abs(y);
        z = std::abs(z);

        if (std::isinf(x) | std::isinf(y) | std::isinf(z))  [[unlikely]]
                return limits::infinity();
        if (std::isnan(x) | std::isnan(y) | std::isnan(z))      [[unlikely]]
                return limits::quiet_NaN();
        if ((x==zero) & (y==zero) & (z==zero))  [[unlikely]]
                return zero;
        if ((y==zero) & (z==zero))      [[unlikely]]
                return x;
        if ((x==zero) & (z==zero))      [[unlikely]]
                return y;
        if ((x==zero) & (y==zero))      [[unlikely]]
                return z;

        auto sort = [](T& a, T& b, T& c)        constexpr noexcept -> void
        {
                if (a > b) std::swap(a, b);
                if (b > c) std::swap(b, c);
                if (a > b) std::swap(a, b);
        };

        sort(x, y, z);  //      x <= y <= z

        const T
                scale = (z > sqrtmax) ? scale_down : (z < 1) ? scale_up : 1;

        x *= scale;
        y *= scale;
        z *= scale;

        T
                sum = x*x + y*y;

        sum += z*z;
        return std::sqrt(sum) / scale;
}


regards
Gero

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (9 preceding siblings ...)
  2024-03-06  2:47 ` g.peterhoff@t-online.de
@ 2024-03-06  9:43 ` mkretz at gcc dot gnu.org
  2024-03-06 12:35 ` redi at gcc dot gnu.org
                   ` (8 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: mkretz at gcc dot gnu.org @ 2024-03-06  9:43 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #20 from Matthias Kretz (Vir) <mkretz at gcc dot gnu.org> ---
Thanks, I'd be very happy if such a relatively clear implementation could make
it!

> branchfree code is always better.

Don't say it like that. Smart branching, making use of how static
branch-prediction works, can speed up code significantly. You don't want to
compute everything when 99.9% of the inputs need only a fraction of the work.

              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
 float, simd_abi::scalar                   48.1           1             17     
     1
 float, std::hypot                         43.3        1.11           12.3     
  1.39
 float, hypot3_scale                       31.7        1.52           22.3     
 0.764
 float, hypot3_exp                         83.9       0.574           84.5     
 0.201
--------------------------------------------------------------------------------------
              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
double, simd_abi::scalar                   54.7           1             15     
     1
double, std::hypot                         53.8        1.02             19     
  0.79
double, hypot3_scale                         44        1.24             24     
 0.625
double, hypot3_exp                         91.3       0.599             91     
 0.165

and with -ffast-math:

              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
 float, simd_abi::scalar                   48.9           1           9.15     
     1
 float, std::hypot                         53.2       0.918           8.31     
   1.1
 float, hypot3_scale                       31.3        1.56             14     
 0.652
 float, hypot3_exp                         55.9       0.874           21.5     
 0.425
--------------------------------------------------------------------------------------
              TYPE                      Latency     Speedup     Throughput    
Speedup
                                  [cycles/call] [per value]  [cycles/call] [per
value]
double, simd_abi::scalar                   54.8           1           9.07     
     1
double, std::hypot                         61.5       0.891           11.3     
 0.805
double, hypot3_scale                       40.8        1.34           12.1     
 0.753
double, hypot3_exp                         64.2       0.853           23.3     
  0.39


I have not tested correctness or precision yet. Also, the benchmark only uses
inputs that do not require anything else than √x²+y²+z² (which, I believe,
should be the common input and thus optimized for).

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (10 preceding siblings ...)
  2024-03-06  9:43 ` mkretz at gcc dot gnu.org
@ 2024-03-06 12:35 ` redi at gcc dot gnu.org
  2024-03-12 11:31 ` mkretz at gcc dot gnu.org
                   ` (7 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: redi at gcc dot gnu.org @ 2024-03-06 12:35 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #21 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to g.peterhoff from comment #19)
> * You were probably wondering why I wrote "if (std::isinf(x) | std::isinf(y)
> | std::isinf(z))", for example. This is intentional. The problem is that gcc
> almost always produces branch code for logical operations, so *a lot* of
> conditional jumps. By using arithmetic operations, so instead of || && just
> | &, I can get it to generate only actually necessary conditional jumps or
> cmoves. branchfree code is always better.

In general the code is not equivalent if any of the subexpressions has side
effects, such as setting errno, raising an exception for a signalling nan etc.
but I don't think we care about that here.

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (11 preceding siblings ...)
  2024-03-06 12:35 ` redi at gcc dot gnu.org
@ 2024-03-12 11:31 ` mkretz at gcc dot gnu.org
  2024-03-19  0:09 ` g.peterhoff@t-online.de
                   ` (6 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: mkretz at gcc dot gnu.org @ 2024-03-12 11:31 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #22 from Matthias Kretz (Vir) <mkretz at gcc dot gnu.org> ---
I took your hypot3_scale and reduced latency and throughput. I don't think the
sqrtmax/sqrtmin limits are correct (sqrtmax² * 3 -> infinity).

              TYPE                      Latency     Speedup     Throughput    
Speedup                                                                         
                                  [cycles/call] [per value]  [cycles/call] [per
value]                                                                          
 float,                                    46.5           1           12.7     
     1                                                                          
 float, hypot3_scale                       35.5        1.31             27     
  0.47                                                                          
 float, hypot3_mkretz                      30.2        1.54             12     
  1.06                                                                          
-------------------------------------------------------------------------------------- 
              TYPE                      Latency     Speedup     Throughput    
Speedup                                                                         
                                  [cycles/call] [per value]  [cycles/call] [per
value]                                                                          
double,                                    59.6           1             60     
     1                                                                          
double, hypot3_scale                       51.2        1.16           48.8     
  1.23                                                                          
double, hypot3_mkretz                      40.1        1.49             35     
  1.71


template <typename T>
  constexpr T 
  hypot(T x, T y, T z)
  { 
    using limits = std::numeric_limits<T>;

    auto prev_power2 = [](const T value) constexpr noexcept -> T 
    { 
      return std::exp2(std::floor(std::log2(value)));
    };

    constexpr T sqrtmax = std::sqrt(limits::max());
    constexpr T sqrtmin = std::sqrt(limits::min());
    constexpr T scale_up = prev_power2(sqrtmax);
    constexpr T scale_down = T(1) / scale_up;
    constexpr T zero = 0;

    if (not (std::isnormal(x) && std::isnormal(y) && std::isnormal(z)))
[[unlikely]]
      { 
        if (std::isinf(x) | std::isinf(y) | std::isinf(z))
          return limits::infinity();
        else if (std::isnan(x) | std::isnan(y) | std::isnan(z))
          return limits::quiet_NaN();
        const bool xz = x == zero;
        const bool yz = y == zero;
        const bool zz = z == zero;
        if (xz)
          { 
            if (yz)
              return zz ? zero : z;
            else if (zz)
              return y;
          } 
        else if (yz && zz)
          return x;
      } 

    x = std::abs(x);
    y = std::abs(y);
    z = std::abs(z);

    T a = std::max(std::max(x, y), z);
    T b = std::min(std::max(x, y), z);
    T c = std::min(x, y);

    if (a >= sqrtmin && a <= sqrtmax) [[likely]]
      return std::sqrt(__builtin_assoc_barrier(c * c + b * b) + a * a);

    const T scale = a >= sqrtmin ? scale_down : scale_up;

    a *= scale;
    b *= scale;
    c *= scale;

    return std::sqrt(__builtin_assoc_barrier(c * c + b * b) + a * a) / scale;
  }

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (12 preceding siblings ...)
  2024-03-12 11:31 ` mkretz at gcc dot gnu.org
@ 2024-03-19  0:09 ` g.peterhoff@t-online.de
  2024-03-25 10:06 ` mkretz at gcc dot gnu.org
                   ` (5 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-03-19  0:09 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #23 from g.peterhoff@t-online.de ---
Hello Matthias,
you've given me new ideas. I think we agree on implementing hypot3 using a
scaling factor. But the correct value is not yet implemented here either; do
you have a suggestion?
A version here: https://godbolt.org/z/Gd53cG9YG
I've intentionally broken hypot_gp into small pieces so that you can play
around with it. This is of course unnecessary for a final version.

General
* The function must of course work efficiently with all FP types.

Questions
* Sorting: It is theoretically sufficient to sort the values x,y,z only to the
extent that the condition x,y <= z is fulfilled (HYPOT_SORT_FULL).
* Accuracy: This is better with fma (HYPOT_FMA).
* How do you create the benchmarks? I could do this myself without getting on
your nerves.

thx
Gero

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (13 preceding siblings ...)
  2024-03-19  0:09 ` g.peterhoff@t-online.de
@ 2024-03-25 10:06 ` mkretz at gcc dot gnu.org
  2024-04-05  1:08 ` g.peterhoff@t-online.de
                   ` (4 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: mkretz at gcc dot gnu.org @ 2024-03-25 10:06 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #24 from Matthias Kretz (Vir) <mkretz at gcc dot gnu.org> ---
(In reply to g.peterhoff from comment #23)
> * How do you create the benchmarks?

https://github.com/mattkretz/simd-benchmarks
Look at hypot3.cpp :)

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (14 preceding siblings ...)
  2024-03-25 10:06 ` mkretz at gcc dot gnu.org
@ 2024-04-05  1:08 ` g.peterhoff@t-online.de
  2024-04-05  1:23 ` g.peterhoff@t-online.de
                   ` (3 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-04-05  1:08 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #25 from g.peterhoff@t-online.de ---
Hi Matthias,
to get good results on average (all FP-types: (B)FP16..FP128,
scalar/vectorized(SIMD)/parallel/...) this algorithm seems to me (so far) to be
suitable:

template <typename Type>
inline constexpr Type   hypot_gp(Type x, Type y, Type z)        noexcept
{
        using limits = std::numeric_limits<Type>;

        constexpr Type
                sqrt_min        = std::sqrt(limits::min()),
                sqrt_max        = std::sqrt(limits::max()),
                scale_up        = std::exp2( Type(limits::max_exponent/2)),
                scale_down      = std::exp2(-Type(limits::max_exponent/2)),
                zero            = 0;

        x = std::abs(x);
        y = std::abs(y);
        z = std::abs(z);

        if (!(std::isnormal(x) && std::isnormal(y) && std::isnormal(z)))
[[unlikely]]
        {
                if (std::isinf(x) | std::isinf(y) | std::isinf(z))      return
limits::infinity();
                else if (std::isnan(x) | std::isnan(y) | std::isnan(z)) return
limits::quiet_NaN();
                else
                {
                        const bool
                                xz{x == zero},
                                yz{y == zero},
                                zz{z == zero};

                        if (xz)
                        {
                                if (yz)         return zz ? zero : z;
                                else if (zz)    return y;
                        }
                        else if (yz && zz)      return x;
                }
        }

        if (x > z) std::swap(x, z);
        if (y > z) std::swap(y, z);

        if ((z >= sqrt_min) && (z <= sqrt_max)) [[likely]]
        {
                //      no scale
                return std::sqrt(__builtin_assoc_barrier(x*x + y*y) + z*z);
        }
        else
        {
                const Type
                        scale = (z >= sqrt_min) ? scale_down : scale_up;

                x *= scale;
                y *= scale;
                z *= scale;
                return std::sqrt(__builtin_assoc_barrier(x*x + y*y) + z*z);
        }
}

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (15 preceding siblings ...)
  2024-04-05  1:08 ` g.peterhoff@t-online.de
@ 2024-04-05  1:23 ` g.peterhoff@t-online.de
  2024-04-10 16:41 ` g.peterhoff@t-online.de
                   ` (2 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-04-05  1:23 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #26 from g.peterhoff@t-online.de ---
must of course be "... / scale".
How can I still edit posts?

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (16 preceding siblings ...)
  2024-04-05  1:23 ` g.peterhoff@t-online.de
@ 2024-04-10 16:41 ` g.peterhoff@t-online.de
  2024-04-10 16:48 ` jakub at gcc dot gnu.org
  2024-04-10 18:08 ` g.peterhoff@t-online.de
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-04-10 16:41 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #27 from g.peterhoff@t-online.de ---
Hi Matthias,
thanks for your benchmark. I still have 2 questions:

1) Accuracy
The frexp/ldexp variant seems to be the most accurate; is that correct? Then
other constants would have to be used in hypot_gp:
scale_up   = std::exp2(Type(limits::max_exponent-1))
scale_down = std::exp2(Type(limits::min_exponent-1))

2) Speed
Your benchmark outputs several columns (Δ)Latency/(Δ)Throughput/Speedup. What
exactly do the values stand for; what should be optimized for?

thx
Gero

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (17 preceding siblings ...)
  2024-04-10 16:41 ` g.peterhoff@t-online.de
@ 2024-04-10 16:48 ` jakub at gcc dot gnu.org
  2024-04-10 18:08 ` g.peterhoff@t-online.de
  19 siblings, 0 replies; 20+ messages in thread
From: jakub at gcc dot gnu.org @ 2024-04-10 16:48 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #28 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
As long as the scale is a power of two or 1.0 / power of two, I don't see why
any version wouldn't be inaccurate.

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

* [Bug libstdc++/77776] C++17 std::hypot implementation is poor
       [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
                   ` (18 preceding siblings ...)
  2024-04-10 16:48 ` jakub at gcc dot gnu.org
@ 2024-04-10 18:08 ` g.peterhoff@t-online.de
  19 siblings, 0 replies; 20+ messages in thread
From: g.peterhoff@t-online.de @ 2024-04-10 18:08 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77776

--- Comment #29 from g.peterhoff@t-online.de ---
(In reply to Jakub Jelinek from comment #28)
> As long as the scale is a power of two or 1.0 / power of two, I don't see
> why any version wouldn't be inaccurate.

Yes, but the constant scale_up is incorrectly selected.
scale_up = std::exp2(Type(limits::max_exponent-1)) --> ok
scale_up = std::exp2(Type(limits::max_exponent/2)) --> error
scale_up = prev_power2(sqrt_max) --> error

scale_down = std::exp2(Type(limits::min_exponent-1))
also seems to me to be more favorable.

PS:
There seems to be a problem with random numbers and std::float16_t, which is
why I use std::uniform_real_distribution<std::float128_t>. I have not yet found
out exactly where the error lies.

thx
Gero


template <std::floating_point Type>
inline constexpr Type   hypot_exp(Type x, Type y, Type z)       noexcept
{
        using limits = std::numeric_limits<Type>;

        constexpr Type
                zero = 0;

        x = std::abs(x);
        y = std::abs(y);
        z = std::abs(z);

        if (!(std::isnormal(x) && std::isnormal(y) && std::isnormal(z)))
[[unlikely]]
        {
                if              (std::isinf(x) | std::isinf(y) | std::isinf(z))
return limits::infinity();
                else if (std::isnan(x) | std::isnan(y) | std::isnan(z)) return
limits::quiet_NaN();
                else
                {
                        const bool
                                xz{x == zero},
                                yz{y == zero},
                                zz{z == zero};

                        if (xz)
                        {
                                if (yz)                 return zz ? zero : z;
                                else if (zz)    return y;
                        }
                        else if (yz && zz)      return x;
                }
        }

        if (x > z) std::swap(x, z);
        if (y > z) std::swap(y, z);

        int
                exp;

        z = std::frexp(z, &exp);
        y = std::ldexp(y, -exp);
        x = std::ldexp(x, -exp);
        return std::ldexp(std::sqrt(__builtin_assoc_barrier(x*x + y*y) + z*z),
exp);
}

template <std::floating_point Type>
inline constexpr Type   hypot_gp(Type x, Type y, Type z)        noexcept
{
        using limits = std::numeric_limits<Type>;

        constexpr Type
                sqrt_min        = std::sqrt(limits::min()),
                sqrt_max        = std::sqrt(limits::max()),
                scale_up        = std::exp2(Type(limits::max_exponent-1)),
                scale_down      = std::exp2(Type(limits::min_exponent-1)),
                zero            = 0;

        x = std::abs(x);
        y = std::abs(y);
        z = std::abs(z);

        if (!(std::isnormal(x) && std::isnormal(y) && std::isnormal(z)))
[[unlikely]]
        {
                if              (std::isinf(x) | std::isinf(y) | std::isinf(z))
return limits::infinity();
                else if (std::isnan(x) | std::isnan(y) | std::isnan(z)) return
limits::quiet_NaN();
                else
                {
                        const bool
                                xz{x == zero},
                                yz{y == zero},
                                zz{z == zero};

                        if (xz)
                        {
                                if (yz)                 return zz ? zero : z;
                                else if (zz)    return y;
                        }
                        else if (yz && zz)      return x;
                }
        }

        if (x > z) std::swap(x, z);
        if (y > z) std::swap(y, z);

        if (const bool b{z>=sqrt_min}; b && z<=sqrt_max) [[likely]]
        {
                //      no scale
                return std::sqrt(__builtin_assoc_barrier(x*x + y*y) + z*z);
        }
        else
        {
                const Type
                        scale = b ? scale_down : scale_up;

                x *= scale;
                y *= scale;
                z *= scale;
                return std::sqrt(__builtin_assoc_barrier(x*x + y*y) + z*z) /
scale;
        }
}

template <std::floating_point Type>
void    test(const size_t count, const Type min, const Type max, const Type
factor)
{
        std::random_device rd{};
        std::mt19937 gen{rd()};
        std::uniform_real_distribution<std::float128_t> dis{min, max};

        auto rnd = [&]() noexcept -> Type { return Type(dis(gen) * factor); };

        for (size_t i=0; i<count; ++i)
        {
                const Type
                        x = rnd(),
                        y = rnd(),
                        z = rnd(),
                        r0= hypot_exp(x, y, z),
                        r1= hypot_gp(x, y, z);

                if (r0 != r1) [[unlikely]]
                {
                        std::cout << "error\n";
                        return;
                }
        }
        std::cout << "ok\n";
}

int main()
{
        using value_type = std::float64_t;
        using limits = std::numeric_limits<value_type>;

        test<value_type>(1024*1024, 0.5, 1, limits::max());
        test<value_type>(1024*1024, 0, 1, limits::min());

        return EXIT_SUCCESS;
}

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

end of thread, other threads:[~2024-04-10 18:08 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <bug-77776-4@http.gcc.gnu.org/bugzilla/>
2021-05-04 12:31 ` [Bug libstdc++/77776] C++17 std::hypot implementation is poor rguenth at gcc dot gnu.org
2024-02-29 18:23 ` g.peterhoff@t-online.de
2024-03-01 12:27 ` redi at gcc dot gnu.org
2024-03-02 15:52 ` g.peterhoff@t-online.de
2024-03-04  3:49 ` de34 at live dot cn
2024-03-04 10:45 ` mkretz at gcc dot gnu.org
2024-03-04 11:12 ` jakub at gcc dot gnu.org
2024-03-04 17:14 ` mkretz at gcc dot gnu.org
2024-03-04 20:21 ` jakub at gcc dot gnu.org
2024-03-06  2:47 ` g.peterhoff@t-online.de
2024-03-06  9:43 ` mkretz at gcc dot gnu.org
2024-03-06 12:35 ` redi at gcc dot gnu.org
2024-03-12 11:31 ` mkretz at gcc dot gnu.org
2024-03-19  0:09 ` g.peterhoff@t-online.de
2024-03-25 10:06 ` mkretz at gcc dot gnu.org
2024-04-05  1:08 ` g.peterhoff@t-online.de
2024-04-05  1:23 ` g.peterhoff@t-online.de
2024-04-10 16:41 ` g.peterhoff@t-online.de
2024-04-10 16:48 ` jakub at gcc dot gnu.org
2024-04-10 18:08 ` g.peterhoff@t-online.de

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).