On Wed, Dec 17, 2025 at 11:12 AM Jakub Jelinek <[email protected]> wrote:
> On Wed, Dec 17, 2025 at 04:30:17AM -0500, Nathan Myers wrote: > > > So > > > constexpr _RealT __Rk = _RealT(_UInt(1) << (__log2_Rk - 1)) * 2.0; > > > can be done in that case even using unsigned long long type. > > > > Shifting uint64_t(1) left 64 places, as would occur for > > __d == 64 and _RealT=long double (with its 64-bit mantissa) > > is UB and ill-formed in constexpr context... > > But if __log2_Rk is at most 64, then the above can still be done in > uint64_t. > > > Emulating 128-bit integer operations seems like heroics unless C++ > > is not expected ever to get __int128. The intention was to support > > I think __int128 support is very unlikely coming for 32-bit targets, > both for C and C++. First of all, each target would need to define > ABI for it (what alignment it has, how it is passed, how it is returned). > As shown with _BitInt, that takes years even after getting it standardized. > _BitInt got into C23, and in GCC 14/15 we got support only for 3 targets > (x86_64, i686, aarch64) and only in GCC 16 we are getting a few others > (s390x, loongarch, maybe riscv, maybe arm 32-bit). > I think getting > https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3666r2.html > in would be easier (but see above, it will take a few further years before > all target maintains do something). > > > 128-bit floating-point formats, with mantissa >= 106 bits, in places > > where users have them. Does it seem likely a quad-precision floating > > point type would be implemented, but not __int128? > > That is a very common thing, std::float128_t is supported on many 32-bit > targets. > Fortunately standard does not require generate_cannonical to work with extended floating point types (including __float128_t), to be precise the behavior is undefined per https://eel.is/c++draft/rand#req.genl-1.4.So emitting static error in that case we are conforming. Still we will need to check if we are causing any regressions here, but the old implementation static_assert is_floating_point_v so we should not cause any, unless that allows __float128. > > For __generate_canonical_pow2 I think something like untested class > below could do the job. > __generate_canonical_any would be harder, that needs > multiplication/division, and from what I can see, all this stuff is > compiled > with C++11 as well and so for constexpr we are limited by the C++11 return > stmt only requirement. > > --- libstdc++-v3/include/bits/random.tcc.jj 2025-12-17 > 09:16:43.300578917 +0100 > +++ libstdc++-v3/include/bits/random.tcc 2025-12-17 > 10:58:32.541786465 +0100 > @@ -3481,6 +3481,64 @@ namespace __detail > #pragma GCC diagnostic ignored "-Wc++14-extensions" // for variable > templates > #pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr > > +#ifndef __SIZEOF_INT128__ > + namespace __detail > + { > + struct __rng_uint128 { > + uint64_t _M_l, _M_h; > + > + constexpr __rng_uint128(uint64_t __x) noexcept > + : _M_l (__x), _M_h (0) {} > + > + constexpr __rng_uint128(uint64_t __h, uint64_t __l) noexcept > + : _M_l (__l), _M_h (__h) {} > + > + template<typename _RealT> > + constexpr operator _RealT() noexcept { > + return _M_h == 0 ? _RealT(_M_l) > + : _RealT(_M_h) * _RealT(18446744073709551616.0) > + + _RealT(_M_l); > + } > + }; > + > + constexpr __rng_uint128 > + operator+(const __rng_uint128& __x, const __rng_uint128& __y) noexcept > + { > + return __rng_uint128(__x._M_h + __y._M_h > + + (__x._M_l + __y._M_l < __x._M_l), > + __x._M_l + __y._M_l); > + } > + > + constexpr __rng_uint128 > + operator<<(const __rng_uint128& __x, size_t __y) noexcept > + { > + return (__y >= 64 > + ? __rng_uint128(__x._M_l << (__y - 64), 0) > + : __y > + ? __rng_uint128((__x._M_h << __y) | (__x._M_l >> (64 - __y)), > + __x._M_l << __y) > + : __x); > + } > + > + constexpr __rng_uint128 > + operator>>(const __rng_uint128& __x, size_t __y) noexcept > + { > + return (__y >= 64 > + ? __rng_uint128(0, __x._M_h >> (__y - 64)) > + : __y > + ? __rng_uint128(__x._M_h >> __y, > + (__x._M_l >> __y) | (__x._M_h << (64 - __y))) > + : __x); > + } > + > + constexpr __rng_uint128 > + operator|(const __rng_uint128& __x, const __rng_uint128& __y) noexcept > + { > + return __rng_uint128(__x._M_h | __y._M_h, __x._M_l | __y._M_l); > + } > + } > +#endif > + > // 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 > @@ -3511,7 +3569,8 @@ 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, typename _UInt, size_t __d, typename _Urbg, > + int __log2_uint_max> > _RealT > __generate_canonical_pow2(_Urbg& __urng) > { > @@ -3521,10 +3580,8 @@ namespace __detail > // 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); > // rd = _UInt(1) << __d; // Could overflow, UB. > const unsigned __k = (__d + __log2_R - 1) / __log2_R; > const unsigned __log2_Rk_max = __k * __log2_R; > @@ -3532,7 +3589,8 @@ 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 > @@ -3593,7 +3651,7 @@ namespace __detail > 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) > { > @@ -3670,18 +3728,20 @@ namespace __detail > 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); > + return __generate_canonical_pow2<_RealT, uint32_t, __d, > + 32>(__urng); > else if constexpr (__d <= 64) > - return __generate_canonical_pow2<_RealT, uint64_t, > __d>(__urng); > + return __generate_canonical_pow2<_RealT, uint64_t, __d, > + 64>(__urng); > else > { > -#if defined(__SIZEOF_INT128__) > // Accommodate double double or float128. > +#if defined(__SIZEOF_INT128__) > return __extension__ __generate_canonical_pow2< > - _RealT, unsigned __int128, __d>(__urng); > + _RealT, unsigned __int128, __d, 128>(__urng); > #else > - static_assert(false, > - "float precision >64 requires __int128 support"); > + return __extension__ __generate_canonical_pow2< > + _RealT, __detail::__rng_uint128, __d, 128>(__urng); > #endif > } > } > > > Jakub > >
