[PATCH] libstdc++: Handle generators with non-zero min in uniform_int_distribution [PR118665]
Jonathan Wakely
jwakely@redhat.com
Thu Aug 27 15:17:37 GMT 2026
On Mon, 27 Jul 2026 at 16:21, Tomasz Kamiński <tkaminsk@redhat.com> wrote:
>
> The commit r11-3757-g98c37d3bacbb2f that introduced Lemire's algorithm in
> uniform_int_distribution, assumed that any engine that generates range of
> size 2^32 or 2^64, returns a range starting from zero. While this is true
> for all standard providede engines, it may not be true for user provided
> ranges. In such case the produced output is no longer uniform:
> testDiscreteDist was failing for such engine.
Oops, good catch. I think we were assuming that if the generator's
range is equal to UINT32_MAX or UINT64_MAX then it generates the
entire range of the output, from 0 to
numeric_limits<result_type>::max(). But if result_type is uint64_t we
could have a range of UINT32_MAX that doesn't begin with zero. Nice
test case.
>
> This patch simply substract __min (_Urbg::min()) from the generated number
> (__g()). As this value is compile time constant, this has no performance
> impact for generators producing ranges starting from zero.
>
> PR libstdc++/118665
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/uniform_int_dist.h (uniform_int_distribution::_S_nd):
> Substract __min (_Urbg::min()) from each generator invocation.
> * testsuite/26_numerics/random/uniform_int_distribution/operators/values.cc:
> Add testDiscreteDist for shifted (with non-zero min()) engine.
> ---
> I realized that this is another regressions from Lemiere's algorithm,
> for some unusuall generators, and may affects ones that emit range of
> size 2^N, with N != 32 or != 64.
>
> Testing on x86_64-linux. Additionally *random* test passed
> in all standard modes, debug, -m32. OK for trunk?
OK for trunk.
>
> We should for backport this with the previous change (alternating
> rejection), and may do it idependently.
Yes, this should be on all active branches, even if we don't backport
the alternating rejection to all active branches.
> libstdc++-v3/include/bits/uniform_int_dist.h | 7 +++---
> .../operators/values.cc | 22 +++++++++++++++++++
> 2 files changed, 26 insertions(+), 3 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/uniform_int_dist.h b/libstdc++-v3/include/bits/uniform_int_dist.h
> index 8ffce793482..1b472e7b93a 100644
> --- a/libstdc++-v3/include/bits/uniform_int_dist.h
> +++ b/libstdc++-v3/include/bits/uniform_int_dist.h
> @@ -262,11 +262,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> static_assert(!_Wp_traits::__is_signed, "W must be unsigned");
> static_assert(_Wp_traits::__digits == (2 * _Up_traits::__digits),
> "W must be twice as wide as U");
> + constexpr auto __min = _Urbg::min();
>
> // reference: Fast Random Integer Generation in an Interval
> // ACM Transactions on Modeling and Computer Simulation 29 (1), 2019
> // https://arxiv.org/abs/1805.10941
> - _Wp __product = _Wp(__g()) * _Wp(__range);
> + _Wp __product = _Wp(__g() - __min) * _Wp(__range);
> _Up __low = _Up(__product);
> if (__low < __range)
> {
> @@ -277,12 +278,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> // begining (__low) and end (__high) of the range. This
> // guarantee stop for non-uniform engines that always results
> // in __low value.
> - __product = _Wp(__g()) * _Wp(__range);
> + __product = _Wp(__g() - __min) * _Wp(__range);
> _Up __high = _Up(__product);
> if (__high <= _Up_traits::__max - __threshold)
> break;
>
> - __product = _Wp(__g()) * _Wp(__range);
> + __product = _Wp(__g() - __min) * _Wp(__range);
> __low = _Up(__product);
> }
> }
> diff --git a/libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/values.cc b/libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/values.cc
> index 11758a3e2b6..fce14944d88 100644
> --- a/libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/values.cc
> +++ b/libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/values.cc
> @@ -21,6 +21,7 @@
> // 26.5.8.2.1 Class template uniform_int_distribution [rand.dist.uni.int]
>
> #include <random>
> +#include <cstdint>
> #include <functional>
> #include <testsuite_random.h>
>
> @@ -32,6 +33,19 @@
> # define ARGS
> #endif
>
> +template<std::uint64_t Offset>
> +struct shifted
> +{
> + using result_type = std::uint64_t;
> + static constexpr result_type min() { return Offset; }
> + static constexpr result_type max() { return Offset + std::mt19937::max(); }
> +
> + result_type operator()()
> + { return Offset + eng(); }
> +
> + std::mt19937 eng;
> +};
> +
> void test01()
> {
> using namespace __gnu_test;
> @@ -49,6 +63,14 @@ void test01()
> std::uniform_int_distribution<> uid3(1, 20);
> auto buid3 = std::bind(uid3, eng);
> testDiscreteDist<ARGS>(buid3, [](int n) { return uniform_int_pdf(n, 1, 20); } );
> +
> + shifted<(std::uint64_t(1) << 16)> s16e;
> + auto buid4 = std::bind(uid3, s16e);
> + testDiscreteDist<ARGS>(buid4, [](int n) { return uniform_int_pdf(n, 1, 20); } );
> +
> + shifted<(std::uint64_t(1) << 32)> s32e;
> + auto buid5 = std::bind(uid3, s32e);
> + testDiscreteDist<ARGS>(buid5, [](int n) { return uniform_int_pdf(n, 1, 20); } );
> }
>
> int main()
> --
> 2.55.0
>
More information about the Libstdc++
mailing list