libstdc++: Fix generate_canonical for float16_t and float128_t [PR 121926]

Message ID 20250912115126.21006-1-cassio.neri@gmail.com
State New
Headers
Series libstdc++: Fix generate_canonical for float16_t and float128_t [PR 121926] |

Commit Message

Cassio Neri Sept. 12, 2025, 11:51 a.m. UTC
  The current implementation of generate_canonical yields NAN for float16_t and
fails to compile for float128_t on platforms where long double is IBM-128.

Indeed, it sets a long double variable based the provided RNG:

const long double __r = number_of_elements_in_the_range_of_the_RNG;

Later it multiplies __r to a variable __tmp of type _RealType, the provided
floating-point type:

__tmp *= __r;

When _RealType is float16_t the implicit cast from long double to float16_t
might yield infinity and subsequent calculations, notably the returned value,
become NAN.

When _RealType is float128_t (_Float128/__float128) and long double is IBM-128,
compilation fails because multiplication of IBM-128 and float128_t is undefined.

This patch fixes these issues by performing all intermediate calculations using
_CalcType which is the largest between _RealType and double. The end result is
cast to _RealType and returned.

_CalcType being at least double is probably enough to prevent __r from being
infinity. Nevertheless, this is static_asserted and, in case of failure, the
user is informed that the combination of _RealType and the provided RNG is
invalid.

	PR libstdc++/121926

libstdc++-v3/ChangeLog:

	* include/bits/random.h: Prevent narrowing warning in
	  uniform_real_distribution.
	* include/bits/random.tcc: Fix generate_canonical.
	* testsuite/26_numerics/random/uniform_real_distribution/cons/stdfloat-c++23.cc:
	  New test.
---
 libstdc++-v3/include/bits/random.h            |  2 +-
 libstdc++-v3/include/bits/random.tcc          | 34 ++++++++---
 .../cons/stdfloat-c++23.cc                    | 60 +++++++++++++++++++
 3 files changed, 86 insertions(+), 10 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/cons/stdfloat-c++23.cc
  

Comments

Jonathan Wakely Sept. 12, 2025, 12:13 p.m. UTC | #1
On Fri, 12 Sept 2025 at 12:51, Cassio Neri wrote:
>
> The current implementation of generate_canonical yields NAN for float16_t and
> fails to compile for float128_t on platforms where long double is IBM-128.

Hi Cassio,

The standard doesn't require anything in <random> to work with
extended floating-point types, only with float, double and long
double.  If making 16-bit FP types work is doable without too much
effort, that seems fine, but supporting std::float128_t for IBM
double-double targets is not a good use of anybody's time.

Nathan is in the middle of rewriting generate_canonical, the latest
iteration of the patch is at
https://gcc.gnu.org/pipermail/gcc-patches/2025-September/695230.html
  

Patch

diff --git a/libstdc++-v3/include/bits/random.h b/libstdc++-v3/include/bits/random.h
index 1fdaf51934f..b6ec07164ac 100644
--- a/libstdc++-v3/include/bits/random.h
+++ b/libstdc++-v3/include/bits/random.h
@@ -1930,7 +1930,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
        *
        * The lower bound is set to 0.0 and the upper bound to 1.0
        */
-      uniform_real_distribution() : uniform_real_distribution(0.0) { }
+      uniform_real_distribution() : uniform_real_distribution(_RealType(0)) { }
 
       /**
        * @brief Constructs a uniform_real_distribution object.
diff --git a/libstdc++-v3/include/bits/random.tcc b/libstdc++-v3/include/bits/random.tcc
index 53ccacb2e38..2c10007d7e8 100644
--- a/libstdc++-v3/include/bits/random.tcc
+++ b/libstdc++-v3/include/bits/random.tcc
@@ -3357,23 +3357,39 @@  namespace __detail
       static_assert(std::is_floating_point<_RealType>::value,
 		    "template argument must be a floating point type");
 
+      using _CalcType = typename std::conditional<
+        sizeof(_RealType) >= sizeof(double), _RealType, double>::type;
+
       const size_t __b
-	= std::min(static_cast<size_t>(std::numeric_limits<_RealType>::digits),
+	= std::min(static_cast<size_t>(std::numeric_limits<_CalcType>::digits),
                    __bits);
-      const long double __r = static_cast<long double>(__urng.max())
-			    - static_cast<long double>(__urng.min()) + 1.0L;
-      const size_t __log2r = std::log(__r) / std::log(2.0L);
+      constexpr _CalcType __r = static_cast<_CalcType>(__urng.max())
+                              - static_cast<_CalcType>(__urng.min())
+                              + _CalcType(1);
+
+#if _GLIBCXX_USE_C99_MATH_FUNCS
+      constexpr bool __is_finite = std::isfinite(__r);
+#else
+      constexpr bool __is_finite =
+	__r < std::numeric_limits<_CalcType>::infinity();
+#endif
+      static_assert(__r > _CalcType(0) && __is_finite,
+                    "invalid combination of floating point type and generator");
+
+      const size_t __log2r = std::log(__r) / std::log(_CalcType(2));
       const size_t __m = std::max<size_t>(1UL,
 					  (__b + __log2r - 1UL) / __log2r);
-      _RealType __ret;
-      _RealType __sum = _RealType(0);
-      _RealType __tmp = _RealType(1);
+      _CalcType __res;
+      _CalcType __sum = _CalcType(0);
+      _CalcType __tmp = _CalcType(1);
       for (size_t __k = __m; __k != 0; --__k)
 	{
-	  __sum += _RealType(__urng() - __urng.min()) * __tmp;
+	  __sum += _CalcType(__urng() - __urng.min()) * __tmp;
 	  __tmp *= __r;
 	}
-      __ret = __sum / __tmp;
+      __res = __sum / __tmp;
+
+      _RealType __ret = static_cast<_RealType>(__res);
       if (__builtin_expect(__ret >= _RealType(1), 0))
 	{
 #if _GLIBCXX_USE_C99_MATH_FUNCS
diff --git a/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/cons/stdfloat-c++23.cc b/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/cons/stdfloat-c++23.cc
new file mode 100644
index 00000000000..ce78c17021b
--- /dev/null
+++ b/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/cons/stdfloat-c++23.cc
@@ -0,0 +1,60 @@ 
+// { dg-do run { target c++23 } }
+//
+// Copyright (C) 2008-2025 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <random>
+#include <stdfloat>
+#include <testsuite_hooks.h>
+
+template <typename Float>
+  void
+  test()
+  {
+    std::uniform_real_distribution<Float> u;
+    VERIFY( u.a() == Float{0} );
+    VERIFY( u.b() == Float{1} );
+    VERIFY( u.min() == Float{0} );
+    VERIFY( u.max() == Float{1} );
+
+    auto device = std::mt19937_64{};
+    for (unsigned n = 0; n < 10; ++n)
+    {
+      auto const x = u(device);
+      VERIFY( x >= Float{0} );
+      VERIFY( x <  Float{1} );
+    }
+  }
+
+int main()
+{
+#ifdef __STDCPP_FLOAT16_T__
+  test<std::float16_t>();
+#endif
+#ifdef __STDCPP_FLOAT32_T__
+  test<std::float32_t >();
+#endif
+#ifdef __STDCPP_FLOAT64_T__
+  test<std::float64_t >();
+#endif
+#ifdef __STDCPP_FLOAT128_T__
+  test<std::float128_t>();
+#endif
+#ifdef __STDCPP_BFLOAT16_T__
+  test<std::bfloat16_t>();
+#endif
+}