[PATCH v2] libstdc++: Fix up std::generate_canonical for 32-bit arches

Jonathan Wakely jwakely@redhat.com
Thu Dec 18 09:42:53 GMT 2025


Make use of __detail::_Select_uint_least_t<d>::type for
std::generate_canonical, so that we choose an appropriately sized
integer based on the number of bits needed, and so we have a 128-bit
integer type even on 32-bit targets (via the new __rand_uint128 class).

libstdc++-v3/ChangeLog:

	* include/bits/random.tcc (__generate_canonical_pow2): Adjust
	comments. Remove _UInt template parameter and define it in the
	body using _Select_uint_least_t<__d>. Remove popcount call for
	getting the width of the _UInt type. Cast floating-point
	literal to _RealT.
	(__generate_canonical_any): Remove _UInt template parameter and
	define it in the body using _Select_uint_least_t<__d * 2>. Use
	direct-initialization for _UInt variables. Cast floating-point
	literal to _RealT.
	(generate_canonical): Remove unused typedef. Remove constexpr-if
	branches and remove unsigned type from template argument lists.

Co-authored-by: Jakub Jelinek <jakub@redhat.com>
---

v2: Moved the _UInt type into the function bodies as Tomasz suggested.
Adjusted comment to remove "the norm in real programs" because there are
random engines that do not produce a range of values that fits nicely
into a power of two, such as std::mindstd_rand and std::minstd_rand0
(and our std::default_random_engine, which is std::minstd_rand0).

Testing now on x86_64-linux.

 libstdc++-v3/include/bits/random.tcc | 75 +++++++++-------------------
 1 file changed, 23 insertions(+), 52 deletions(-)

diff --git a/libstdc++-v3/include/bits/random.tcc b/libstdc++-v3/include/bits/random.tcc
index 68eae419b721..0256059ab72c 100644
--- a/libstdc++-v3/include/bits/random.tcc
+++ b/libstdc++-v3/include/bits/random.tcc
@@ -3482,13 +3482,13 @@ namespace __detail
 #pragma GCC diagnostic ignored "-Wc++14-extensions" // for variable templates
 #pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
 
-  // This version is used when Urbg::max()-Urbg::min() is a power of
-  // two less 1, the norm in real programs. It works by calling urng()
-  // as many times as needed to fill the target mantissa, accumulating
-  // entropy into an integer value, converting that to the float type,
-  // and then dividing by the range of the integer value (a constexpr
-  // power of 2, so only adjusts the exponent) to produce a result in
-  // [0..1]. In case of an exact 1.0 result, we re-try.
+  // __generate_canonical_pow2 is used when Urbg::max()-Urbg::min() is
+  // a power of two less 1. It works by calling urng() as many times as
+  // needed to fill the target mantissa, accumulating entropy into an
+  // integer value, converting that to the float type, and then dividing
+  // by the range of the integer value (a constexpr power of 2,
+  // so only adjusts the exponent) to produce a result in [0..1].
+  // In case of an exact 1.0 result, we re-try.
   //
   // It needs to work even when the integer type used is only as big
   // as the float mantissa, such as uint64_t for long double. So,
@@ -3512,20 +3512,21 @@ namespace __detail
   // This implementation works with std::bfloat16, which can exactly
   // represent 2^32, but not with std::float16_t, limited to 2^15.
 
-  template<typename _RealT, typename _UInt, size_t __d, typename _Urbg>
+  template<typename _RealT, size_t __d, typename _Urbg>
     _RealT
     __generate_canonical_pow2(_Urbg& __urng)
     {
+      using _UInt = typename __detail::_Select_uint_least_t<__d>::type;
+
       // Parameter __d is the actual target number of bits.
       // Commented-out assignments below are of values specified in
       //  the Standard, but not used here for reasons noted.
       // r = 2;  // Redundant, we only support radix 2.
       using _Rng = decltype(_Urbg::max());
       const _Rng __rng_range_less_1 = _Urbg::max() - _Urbg::min();
-      const _UInt __uint_range_less_1 = ~_UInt(0);
       // R = _UInt(__rng_range_less_1) + 1;  // May wrap to 0.
       const auto __log2_R = __builtin_popcountg(__rng_range_less_1);
-      const auto __log2_uint_max = __builtin_popcountg(__uint_range_less_1);
+      const auto __log2_uint_max = sizeof(_UInt) * __CHAR_BIT__;
       // rd = _UInt(1) << __d;  // Could overflow, UB.
       const unsigned __k = (__d + __log2_R - 1) / __log2_R;
       const unsigned __log2_Rk_max = __k * __log2_R;
@@ -3533,7 +3534,7 @@ namespace __detail
 	__log2_uint_max < __log2_Rk_max ? __log2_uint_max : __log2_Rk_max;
       static_assert(__log2_Rk >= __d);
       // Rk = _UInt(1) << __log2_Rk;  // Likely overflows, UB.
-      constexpr _RealT __Rk = _RealT(_UInt(1) << (__log2_Rk - 1)) * 2.0;
+      constexpr _RealT __Rk = _RealT(_UInt(1) << (__log2_Rk - 1)) * _RealT(2.0);
 #if defined(_GLIBCXX_GENERATE_CANONICAL_STRICT)
       const unsigned __log2_x = __log2_Rk - __d; // # of spare entropy bits.
 #else
@@ -3582,29 +3583,31 @@ namespace __detail
   // (the range of values produced by the rng) up to twice the length
   // of the mantissa.
 
-  template<typename _RealT, typename _UInt, size_t __d, typename _Urbg>
+  template<typename _RealT, size_t __d, typename _Urbg>
     _RealT
     __generate_canonical_any(_Urbg& __urng)
     {
-      static_assert(__d < __builtin_popcountg(~_UInt(0)));
+      using _UInt = typename __detail::_Select_uint_least_t<__d * 2>::type;
+
       // Names below are chosen to match the description in the Standard.
       // Parameter d is the actual target number of bits.
-      const _UInt __r = 2;
+      const _UInt __r{2};
       const _UInt __R = _UInt(_Urbg::max() - _Urbg::min()) + 1;
       const _UInt __rd = _UInt(1) << __d;
       const unsigned __k = __gen_can_rng_calls_needed(__R, __rd);
       const _UInt __Rk = __gen_can_pow(__R, __k);
-      const _UInt __x =  __Rk/__rd;
+      const _UInt __x =  __Rk / __rd;
 
       while (true)
 	{
-	  _UInt __Ri = 1, __sum = __urng() - _Urbg::min();
+	  _UInt __Ri{1};
+	  _UInt __sum{__urng() - _Urbg::min()};
 	  for (int __i = __k - 1; __i > 0; --__i)
 	    {
 	      __Ri *= __R;
-	      __sum += (__urng() - _Urbg::min()) * __Ri;
+	      __sum += _UInt{__urng() - _Urbg::min()} * __Ri;
 	    }
-	  const _RealT __ret = _RealT(__sum / __x) / __rd;
+	  const _RealT __ret = _RealT(__sum / __x) / _RealT(__rd);
 	  if (__ret < _RealT(1.0))
 	    return __ret;
 	}
@@ -3660,47 +3663,15 @@ _GLIBCXX_BEGIN_INLINE_ABI_NAMESPACE(_V2)
 	"float16_t type is not supported, consider using bfloat16_t");
 #endif
 
-      using _Rng = decltype(_Urbg::max());
       const unsigned __d_max = std::numeric_limits<_RealT>::digits;
       const unsigned __d = __digits > __d_max ? __d_max : __digits;
 
       // If the RNG range is a power of 2 less 1, the float type mantissa
       // is enough bits. If not, we need more.
       if constexpr (__is_power_of_2_less_1(_Urbg::max() - _Urbg::min()))
-	{
-	  if constexpr (__d <= 32)
-	    return __generate_canonical_pow2<_RealT, uint32_t, __d>(__urng);
-	  else if constexpr (__d <= 64)
-	    return __generate_canonical_pow2<_RealT, uint64_t, __d>(__urng);
-	  else
-	    {
-#if defined(__SIZEOF_INT128__)
-	      // Accommodate double double or float128.
-	      return __extension__ __generate_canonical_pow2<
-		_RealT, unsigned __int128, __d>(__urng);
-#else
-	      static_assert(false,
-		"float precision >64 requires __int128 support");
-#endif
-	    }
-	}
+	return __generate_canonical_pow2<_RealT, __d>(__urng);
       else // Need up to 2x bits.
-	{
-	  if constexpr (__d <= 32)
-	    return __generate_canonical_any<_RealT, uint64_t, __d>(__urng);
-	  else
-	    {
-#if defined(__SIZEOF_INT128__)
-	      static_assert(__d <= 64,
-		"irregular RNG with float precision >64 is not supported");
-	      return __extension__ __generate_canonical_any<
-		_RealT, unsigned __int128, __d>(__urng);
-#else
-	      static_assert(false, "irregular RNG with float precision"
-		 " >32 requires __int128 support");
-#endif
-	    }
-	}
+	return __generate_canonical_any<_RealT, __d>(__urng);
     }
 _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
 
-- 
2.52.0



More information about the Libstdc++ mailing list