On Thu, Dec 18, 2025 at 3:51 PM Tomasz Kamiński <[email protected]> wrote:
> If the span of the range R produced by uniform bit generator U passed to
> generate_canonical is not power of two, we need to use algorithm that
> requires computing power R^k that is greater than 2^d, where d is number
> of digits in mantissa of _RealT. Previously we have used an integer type
> that is has twice as many digits as d. This lead to situation that for
> standared engines that produced such range (like std::minstd_rand0,
> std::minstd_rand, std::ranlux24, ....) an 256bit integer support was
> required for 128bit floats. However, in this cases R^4 provides more
> than d bits of precision, while fitting in 124 bits.
>
> This patch changes the algorithm for computing the number of digits
> required
> to store R^k, based on value of k:
> * bit_width of R in case of k == 1,
> * __d + 1 if R^k fits inside them,
> * __d * 2 otherwise.
> In C++11 mode for architectures that do not support __int128 we cannot
> compute,
> __k at compile time as the required operations on __rand_unint128 as
> support
> since C++14. In this case we use overestimate an number of required bits,
> by
> computing how many power of two that are smaller than R we need. This is
> done
> by computing ceil(d / log2(R)).
>
> We replace __gen_can_pow and __gen_can_rng_calls_needed with
> __gen_can_log(v, b),
> that computes the largest power of b that fits into v. As such number is
> smaller
> than v, the result will always fit in provided value. Both the logirtm and
> the power value are returned in __gen_can_pow-res struct.
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/random.h (__rand_uint128::operator>)
> (__rand_uint128::operator>=): Define.
> * include/bits/random.tcc (__generate_canonical_pow2):
> Adjust for use of __rand_uint128 in C++11.
> (__gen_can_pow, __gen_can_rng_calls_needed): Replace with
> __gen_can_log.
> (__gen_can_log_res, __gen_can_log): Define.
> (__generate_canonical_any): Reworked how _UInt is determined.
> *
> testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon_eng.cc:
> New test.
> ---
> v2 properly limits uses of overestimates to C++11 and 32bit
> architectures.
>
Test passed on x86_64-linux. OK for trunk?
>
> libstdc++-v3/include/bits/random.h | 8 ++
> libstdc++-v3/include/bits/random.tcc | 116 +++++++++++++-----
> .../operators/gencanon_eng.cc | 39 ++++++
> 3 files changed, 133 insertions(+), 30 deletions(-)
> create mode 100644
> libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon_eng.cc
>
> diff --git a/libstdc++-v3/include/bits/random.h
> b/libstdc++-v3/include/bits/random.h
> index 7a9ba0a7a9e..117d63dff7f 100644
> --- a/libstdc++-v3/include/bits/random.h
> +++ b/libstdc++-v3/include/bits/random.h
> @@ -463,9 +463,17 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
> return false;
> }
>
> + friend _GLIBCXX14_CONSTEXPR bool
> + operator>(const type& __l, const type& __r) noexcept
> + { return __r < __l; }
> +
> friend _GLIBCXX14_CONSTEXPR bool
> operator<=(const type& __l, const type& __r) noexcept
> { return !(__r < __l); }
> +
> + friend _GLIBCXX14_CONSTEXPR bool
> + operator>=(const type& __l, const type& __r) noexcept
> + { return !(__l < __r); }
> #endif
>
> friend _GLIBCXX14_CONSTEXPR bool
> diff --git a/libstdc++-v3/include/bits/random.tcc
> b/libstdc++-v3/include/bits/random.tcc
> index 0256059ab72..6b0704f2cca 100644
> --- a/libstdc++-v3/include/bits/random.tcc
> +++ b/libstdc++-v3/include/bits/random.tcc
> @@ -3532,16 +3532,16 @@ namespace __detail
> const unsigned __log2_Rk_max = __k * __log2_R;
> const unsigned __log2_Rk = // Bits of entropy actually obtained:
> __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)) *
> _RealT(2.0);
> + _GLIBCXX14_CONSTEXPR const _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
> const unsigned __log2_x = 0;
> #endif
> - const _UInt __x = _UInt(1) << __log2_x;
> - constexpr _RealT __rd = __Rk / __x;
> + _GLIBCXX14_CONSTEXPR const _UInt __x = _UInt(1) << __log2_x;
> + _GLIBCXX14_CONSTEXPR const _RealT __rd = __Rk / _RealT(__x);
> // xrd = __x << __d; // Could overflow.
>
> while (true)
> @@ -3552,29 +3552,50 @@ namespace __detail
> __shift += __log2_R;
> __sum |= _UInt(__urng() - _Urbg::min()) << __shift;
> }
> - const _RealT __ret = _RealT(__sum >> __log2_x) / __rd;
> + const _RealT __ret = _RealT(__sum >> __log2_x) / _RealT(__rd);
> if (__ret < _RealT(1.0))
> return __ret;
> }
> }
>
> - template <typename _UInt>
> - constexpr _UInt
> - __gen_can_pow(_UInt __base, size_t __exponent)
> +
> + template<typename _UInt>
> + struct __gen_can_log_res
> {
> - _UInt __prod = __base;
> - while (--__exponent != 0) __prod *= __base;
> - return __prod;
> - }
> + unsigned __floor_log;
> + _UInt __floor_pow;
> +
> + constexpr __gen_can_log_res
> + update(_UInt __base) const
> + { return {__floor_log + 1, __floor_pow * __base}; }
> + };
> +
>
> - template <typename _UInt>
> - constexpr unsigned
> - __gen_can_rng_calls_needed(_UInt __R, _UInt __rd)
> + template <typename _UInt1, typename _UInt2,
> + typename _UComm = __conditional_t<(sizeof(_UInt2) >
> sizeof(_UInt1)),
> + _UInt2, _UInt1>>
> + constexpr __gen_can_log_res<_UInt1>
> + __gen_can_log(_UInt1 __val, _UInt2 __base)
> {
> - unsigned __i = 1;
> - for (auto __Ri = __R; __Ri < __rd; __Ri *= __R)
> - ++__i;
> - return __i;
> +#if __cplusplus >= 201402L
> + __gen_can_log_res<_UInt1> __res{0, _UInt1(1)};
> + if (_UComm(__base) > _UComm(__val))
> + return __res;
> +
> + const _UInt1 __base1(__base);
> + do
> + {
> + __val /= __base1;
> + __res = __res.update(__base1);
> + }
> + while (__val >= __base1);
> + return __res;
> +#else
> + return (_UComm(__val) >= _UComm(__base))
> + ? __gen_can_log(__val / _UInt1(__base), _UInt1(__base))
> + .update(_UInt1(__base))
> + : __gen_can_log_res<_UInt1>{0, _UInt1(1)};
> +#endif
> }
>
> // This version must be used when the range of possible RNG results,
> @@ -3587,30 +3608,65 @@ namespace __detail
> _RealT
> __generate_canonical_any(_Urbg& __urng)
> {
> - 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 = _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;
> +#if (__cplusplus >= 201402L) || defined(__SIZEOF_INT128__)
> +# define _GLIBCXX_GEN_CON_CONST constexpr
> +#else
> +# define _GLIBCXX_GEN_CON_CONST const
> +#endif
> +
> + using _UIntR = typename make_unsigned<decltype(_Urbg::max())>::type;
> + // Cannot overflow, as _Urbg::max() - _Urbg::min() is not power of
> + // two minus one
> + constexpr _UIntR __R = _UIntR(_Urbg::max() - _Urbg::min()) + 1;
> +
> + using _UIntrd = typename __detail::_Select_uint_least_t<__d +
> 1>::type;
> + _GLIBCXX_GEN_CON_CONST _UIntrd __rd = _UIntrd(1) << __d;
> + _GLIBCXX_GEN_CON_CONST auto __logRrd = __gen_can_log(__rd, __R);
> + _GLIBCXX_GEN_CON_CONST unsigned __k
> + = __logRrd.__floor_log + (__rd > __logRrd.__floor_pow);
> +
> + constexpr unsigned __log2R
> + = sizeof(_UIntR) * __CHAR_BIT__ - __builtin_clzg(__R) - 1;
> +#if (__cplusplus >= 201402L) || defined(__SIZEOF_INT128__)
> + constexpr unsigned __bits =
> + // R is larger than rd, use as much bits as _R requires
> + (__k == 1) ? __log2R + 1 :
> + // R^k == rd, we can use _UIntrd, i.e. __d + 1
> + (__k == __logRrd.__floor_log) ? __d + 1 :
> + // R^k > rd, but still first into _UIntd,
> + (~_UIntrd(0) / __logRrd.__floor_pow >= _UIntrd(__R)) ? __d + 1 :
> + // R^k will not fit in _Uintrd, we need bit_width(R^(k-1)) +
> bit_width(R),
> + // but next integer will have twice as many bits anyway, so use it
> + __d * 2;
> +#else
> + // the value of __k is not know at compile time in that case
> + constexpr unsigned __kover = (__d + __log2R - 1) / __log2R;
> + constexpr unsigned __bits = __log2R * __kover;
> +#endif
> + using _UInt = typename __detail::_Select_uint_least_t<__bits>::type;
> +
> + _GLIBCXX_GEN_CON_CONST _UInt __Rk
> + = (__k > __logRrd.__floor_log)
> + ? _UInt(__logRrd.__floor_pow) * _UInt(__R)
> + : _UInt(__logRrd.__floor_pow);
> + _GLIBCXX_GEN_CON_CONST _UInt __x = __Rk / __rd;
>
> while (true)
> {
> _UInt __Ri{1};
> - _UInt __sum{__urng() - _Urbg::min()};
> + _UInt __sum(__urng() - _Urbg::min());
> for (int __i = __k - 1; __i > 0; --__i)
> {
> - __Ri *= __R;
> - __sum += _UInt{__urng() - _Urbg::min()} * __Ri;
> + __Ri *= _UInt(__R);
> + __sum += _UInt(__urng() - _Urbg::min()) * __Ri;
> }
> const _RealT __ret = _RealT(__sum / __x) / _RealT(__rd);
> if (__ret < _RealT(1.0))
> return __ret;
> }
> +#undef _GLIBCXX_GEN_CON_CONST
> }
>
> #if !defined(_GLIBCXX_GENERATE_CANONICAL_STRICT)
> diff --git
> a/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon_eng.cc
> b/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon_eng.cc
> new file mode 100644
> index 00000000000..c8d5b5d263b
> --- /dev/null
> +++
> b/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon_eng.cc
> @@ -0,0 +1,39 @@
> +// { dg-do compile { target { c++11 } } }
> +
> +#include <random>
> +
> +template<typename _Real, typename _URBG>
> +void
> +test_engine()
> +{
> + _URBG __engine;
> + (void)std::generate_canonical<_Real, size_t(-1)>(__engine);
> +}
> +
> +template<typename _Real>
> +void
> +test_all_engines()
> +{
> + test_engine<_Real, std::default_random_engine>();
> +
> + test_engine<_Real, std::minstd_rand0>();
> + test_engine<_Real, std::minstd_rand>();
> + test_engine<_Real, std::mt19937>();
> + test_engine<_Real, std::mt19937_64>();
> + test_engine<_Real, std::ranlux24_base>();
> + test_engine<_Real, std::ranlux48_base>();
> + test_engine<_Real, std::ranlux24>();
> + test_engine<_Real, std::ranlux48>();
> + test_engine<_Real, std::knuth_b>();
> +#if __cplusplus > 202302L
> + test_engine<_Real, std::philox4x32>();
> + test_engine<_Real, std::philox4x64>();
> +#endif
> +}
> +
> +int main()
> +{
> + test_all_engines<float>();
> + test_all_engines<double>();
> + test_all_engines<long double>();
> +}
> --
> 2.52.0
>
>