[PATCH] libstdc++: Rework P0952 generate_cannonical tests.
Tomasz Kaminski
tkaminsk@redhat.com
Wed Apr 29 14:24:36 GMT 2026
On Tue, Jan 13, 2026 at 6:23 PM Tomasz Kamiński <tkaminsk@redhat.com> wrote:
> This expands on the changes from test fix r16-6710-gda5a5c55284969:
> * test name now reflect the size of the generator range,
> * extracted code repeated between tests was exctracted to run_generator,
> * expanded non-power of two ranges types to cover all IEC559 floating
> point,
> * select values to test based on the size of mantisa instead of type,
> handling different long double representations.
>
> The test now cover the cases, where mutliple value greater than one are
> produced (and skipped) in the row. To avoid test running infinite loop,
> the number of skips per element is limited by max_skips_per_elem template
> parameter of run_generator.
>
> The values checked in test_2p31m1<double> differs from their old
> test03<double>
> counterpart, as we now request mantissa - 5 bits for each type (48bits for
> ieee64) instead of previously hardoced 30bits.
>
> libstdc++-v3/ChangeLog:
>
> *
> testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon.cc:
> Updated tests.
> ---
> Cleaned up while waiting for other patch build to finish.
>
> The gencanon.cc test passed in all supported standard modes, and
> with/without -m32. OK for trunk?
>
> .../operators/gencanon.cc | 326 +++++++++---------
> 1 file changed, 166 insertions(+), 160 deletions(-)
>
> diff --git
> a/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon.cc
> b/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon.cc
> index de3b0213cf5..ed4348fe4f4 100644
> ---
> a/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon.cc
> +++
> b/libstdc++-v3/testsuite/26_numerics/random/uniform_real_distribution/operators/gencanon.cc
> @@ -15,16 +15,11 @@ struct local_rng : std::mt19937
> static constexpr std::uint64_t min() { return 0; }
> static constexpr std::uint64_t max() { return max_val; }
> std::uint64_t operator()()
> - { return static_cast<std::mt19937&>(*this)() % (max() + 1); }
> + { return std::mt19937::operator()() % (max() + 1); }
> +
> local_rng(std::mt19937 const& arg) : std::mt19937(arg) {}
> };
>
> -// Verify P0952R2 implementation requiring a second round-trip
> -// if first yields exactly 1. In this test, the RNG delivering
> -// 32 bits per call is seeded such that this occurs once on the
> -// sixth iteration for float, and not at all for double.
> -// However, each double iteration requires two calls to the RNG.
> -
> template<typename T>
> int ifloor(T t)
> { return static_cast<int>(std::floor(t)); }
> @@ -34,39 +29,40 @@ int ifloor(__float128 t)
> { return static_cast<int>(t); }
> #endif
>
> -template <typename T, typename RNG>
> -void
> -test01(RNG& rng, RNG& rng2, int& skips)
> +// Verify P0952R2 implementation requiring a second round-trip
> +// if first yields exactly 1.
> +template<typename T, size_t bits,
> + size_t call_per_elem, size_t max_skips_per_elem,
> + typename RNG>
> +int run_generator(const RNG& rng, int& deviation, int& max, int& rms,
> int& zeros)
> {
> - constexpr size_t mantissa = std::numeric_limits<T>::digits;
> - constexpr size_t call_per_elem = (mantissa + 31) / 32;
> -
> - const auto size = 1000000, buckets = 100;
> + constexpr int iters = 1000000, buckets = 100;
> std::array<int, buckets> histo{};
> - int zero = 0;
> - for (auto i = 0; i != size; ++i) {
> - T sample = std::generate_canonical<T, -1u>(rng);
> +
> + RNG rng1(rng), rng2(rng);
> + int skips = 0;
> + for (auto i = 0; i != iters; ++i) {
> + T sample = std::generate_canonical<T, bits>(rng1);
> VERIFY(sample >= T(0.0));
> VERIFY(sample < T(1.0)); // libstdc++/64351
> if (sample == T(0.0)) {
> - ++zero;
> + ++zeros;
> }
> auto bucket = ifloor(sample * buckets);
> ++histo[bucket];
> rng2.discard(call_per_elem);
> - if (rng != rng2) {
> - ++skips;
> + for (int j = 0; j < max_skips_per_elem; ++j) {
> + if (rng1 == rng2)
> + break;
> rng2.discard(call_per_elem);
> - VERIFY(rng == rng2);
> + ++skips;
> }
> + VERIFY(rng1 == rng2);
> }
>
> - if (!std::numeric_limits<T>::is_iec559)
> - return;
> -
> - int deviation = 0, max = 0, rms = 0, devsquare = 0;
> + int devsquare = 0;
> + const int expected = iters / buckets;
> for (int i = 0; i < buckets; ++i) {
> - const auto expected = size / buckets;
> auto count = histo[i];
> auto diff = count - expected;
> if (diff < 0) diff = -diff;
> @@ -76,26 +72,51 @@ test01(RNG& rng, RNG& rng2, int& skips)
> }
> rms = std::sqrt(devsquare);
>
> + return skips;
> +}
> +
> +// In this test, the RNG delivering 32 bits per call is seeded
> +// such that this occurs once on the sixth iteration for float,
> +// and not at all for double and other types requiring two
> +// calls to the RNG.
> +template <typename T>
> +void
> +test_2p32(const std::mt19937& rng)
> +{
> + constexpr size_t mantissa = std::numeric_limits<T>::digits;
> + constexpr size_t call_per_elem = (mantissa + 31) / 32;
> +
> + int deviation = 0, max = 0, rms = 0, zeros = 0;
> + int skips = run_generator<T, -1u, call_per_elem, 1u>
> + (rng, deviation, max, rms, zeros);
> + if (call_per_elem == 1)
> + VERIFY(skips == 1);
> + else // would require constitutive elements with all bits set
> + VERIFY(skips == 0);
> +
> + if (!std::numeric_limits<T>::is_iec559)
> + return;
>
I will move this check to the beginning of the function (as in case for
remaining functions)
to replicate the fix from Jonathan patch, that disabled test for IBM double
double:
https://gcc.gnu.org/pipermail/libstdc++/2026-April/066177.html
> +
> switch (mantissa)
> {
> case 24: // ieee32
> VERIFY(deviation == 7032);
> VERIFY(max == 276);
> VERIFY(rms == 906);
> - VERIFY(zero == 0);
> + VERIFY(zeros == 0);
> break;
> case 53: // ieee64
> case 64: // ieee80
> VERIFY(deviation == 7650);
> VERIFY(max == 259);
> VERIFY(rms == 975);
> - VERIFY(zero == 0);
> + VERIFY(zeros == 0);
> break;
> case 113: // ieee128
> VERIFY(deviation == 9086);
> VERIFY(max == 290);
> VERIFY(rms == 1142);
> - VERIFY(zero == 0);
> + VERIFY(zeros == 0);
> break;
> default:
> VERIFY(false);
> @@ -103,156 +124,141 @@ test01(RNG& rng, RNG& rng2, int& skips)
> }
> }
>
> -// This one is for use with local_rng
> -template <typename T, typename RNG>
> +// Uses a generate that emits range of size 10^6.
> +// The elements are multiplied and then dividided,
> +// skips are more common.
> +template <typename T>
> void
> -test02(RNG& rng, RNG& rng2,
> - int& deviation, int& max, int& rms, int& zero, int& skips)
> +test_10p6(const std::mt19937& rng)
> {
> - const auto size = 1000000, buckets = 100;
> - std::array<int, buckets> histo{};
> - for (auto i = 0; i != size; ++i) {
> - T sample = std::generate_canonical<T, -1u>(rng);
> - VERIFY(sample >= T(0.0));
> - VERIFY(sample < T(1.0)); // libstdc++/64351
> - if (sample == T(0.0)) {
> - ++zero;
> - }
> - auto bucket = static_cast<int>(std::floor(sample * buckets));
> - ++histo[bucket];
> - rng2.discard(2);
> - if (rng != rng2) {
> - ++skips;
> - rng2.discard(2);
> - VERIFY(rng == rng2);
> - }
> - }
> - int devsquare = 0;
> - for (int i = 0; i < buckets; ++i) {
> - const auto expected = size / buckets;
> - auto count = histo[i];
> - auto diff = count - expected;
> - if (diff < 0) diff = -diff;
> - deviation += diff;
> - devsquare += diff * diff;
> - if (diff > max) max = diff;
> + if (!std::numeric_limits<T>::is_iec559)
> + return;
> +
> + constexpr size_t mantissa = std::numeric_limits<T>::digits;
> + static_assert(mantissa < 120);
> + constexpr size_t call_per_elem = (mantissa / 20) + 1;
> + const local_rng<999999ULL> lrng{rng};
> +
> + int deviation = 0, max = 0, rms = 0, zeros = 0;
> + int skips = run_generator<T, -1u, call_per_elem, 2u>
> + (lrng, deviation, max, rms, zeros);
> +
> + switch (mantissa)
> + {
> + case 24: // ieee32
> + VERIFY(skips == 18);
> + VERIFY(deviation == 8146);
> + VERIFY(max == 250);
> + VERIFY(rms == 1021);
> + VERIFY(zeros == 0);
> + break;
> + case 53: // ieee64
> + VERIFY(skips == 211);
> + VERIFY(deviation == 7492);
> + VERIFY(max == 235);
> + VERIFY(rms == 921);
> + VERIFY(zeros == 0);
> + break;
> + case 64: // ieee80
> + VERIFY(skips == 1);
> + VERIFY(deviation == 7774);
> + VERIFY(max == 250);
> + VERIFY(rms == 958);
> + VERIFY(zeros == 0);
> + break;
> + case 113: // ieee128
> + VERIFY(skips == 3074);
> + VERIFY(deviation == 7568);
> + VERIFY(max == 282);
> + VERIFY(rms == 1001);
> + VERIFY(zeros == 0);
> + break;
> + default:
> + VERIFY(false);
> + break;
> }
> - rms = std::sqrt(devsquare);
> }
>
> -// This one is for the edge-case local_rng. It takes a bit count
> -// to use that is smaller than the floating point mantissa's.
> -template <typename T, unsigned bits, typename RNG>
> +// Uses a generate that emits range of size 2^31-1.
> +// It takes a bit count to use that is smaller than the floating point
> mantissa's.
> +template <typename T>
> void
> -test03(RNG& rng, RNG& rng2,
> - int& deviation, int& max, int& rms, int& zero, int& skips)
> +test_2p31m1(const std::mt19937& rng)
> {
> - const auto size = 1000000, buckets = 100;
> - std::array<int, buckets> histo{};
> - for (auto i = 0; i != size; ++i) {
> - T sample = std::generate_canonical<T, bits>(rng);
> - VERIFY(sample >= T(0.0));
> - VERIFY(sample < T(1.0)); // libstdc++/64351
> - if (sample == T(0.0)) {
> - ++zero;
> - }
> - auto bucket = static_cast<int>(std::floor(sample * buckets));
> - ++histo[bucket];
> - rng2.discard(2);
> - if (rng != rng2) {
> - ++skips;
> - rng2.discard(2);
> - VERIFY(rng == rng2);
> - }
> - }
> - int devsquare = 0;
> - for (int i = 0; i < buckets; ++i) {
> - const auto expected = size / buckets;
> - auto count = histo[i];
> - auto diff = count - expected;
> - if (diff < 0) diff = -diff;
> - deviation += diff;
> - devsquare += diff * diff;
> - if (diff > max) max = diff;
> + if (!std::numeric_limits<T>::is_iec559)
> + return;
> +
> + constexpr size_t mantissa = std::numeric_limits<T>::digits;
> + constexpr size_t bits = mantissa - 5;
> + static_assert(bits < 124);
> + constexpr size_t call_per_elem = (bits / 31) + 1;
> + const local_rng<0x07ffffffeULL> lrng{rng};
> +
> + int deviation = 0, max = 0, rms = 0, zeros = 0;
> + int skips = run_generator<T, bits, call_per_elem, 6u>
> + (lrng, deviation, max, rms, zeros);
> +
> + switch (mantissa)
> + {
> + case 24: // ieee32
> + VERIFY(skips == 215);
> + VERIFY(deviation == 7624);
> + VERIFY(max == 217);
> + VERIFY(rms == 933);
> + VERIFY(zeros == 1);
> + break;
> + case 53: // ieee64
> + VERIFY(skips == 62);
> + VERIFY(deviation == 7698);
> + VERIFY(max == 234);
> + VERIFY(rms == 937);
> + VERIFY(zeros == 0);
> + break;
> + case 64: // ieee80
> + VERIFY(skips == 143342);
> + VERIFY(deviation == 7788);
> + VERIFY(max == 296);
> + VERIFY(rms == 977);
> + VERIFY(zeros == 0);
> + break;
> + case 113: // ieee128
> + VERIFY(skips == 8);
> + VERIFY(deviation == 8824);
> + VERIFY(max == 334);
> + VERIFY(rms == 1086);
> + VERIFY(zeros == 0);
> + break;
> + default:
> + VERIFY(false);
> + break;
> }
> - rms = std::sqrt(devsquare);
> }
>
> -void
> -test00()
> +int main()
> {
> std::mt19937 rng(8890);
> std::seed_seq sequence{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
> rng.seed(sequence);
> rng.discard(12 * 629143);
> +
> + test_2p32<float>(rng);
> + test_10p6<float>(rng);
> + test_2p31m1<float>(rng);
> +
> + test_2p32<double>(rng);
> + test_10p6<double>(rng);
> + test_2p31m1<double>(rng);
> +
> + test_2p32<long double>(rng);
> + test_10p6<long double>(rng);
> + test_2p31m1<long double>(rng);
>
> - { // float
> - int skips{};
> - auto rng2{rng};
> - auto rng3{rng};
> - test01<float>(rng2, rng3, skips);
> - VERIFY(skips == 1);
> - }
> - { // double
> - int skips{};
> - auto rng2{rng};
> - auto rng3{rng};
> - test01<double>(rng2, rng3, skips);
> - VERIFY(skips == 0);
> - }
> - { // long double
> - int skips{};
> - auto rng2{rng};
> - auto rng3{rng};
> - test01<long double>(rng2, rng3, skips);
> - VERIFY(skips == 0);
> - }
> #ifndef _GLIBCXX_GENERATE_CANONICAL_STRICT
> # ifdef __SIZEOF_FLOAT128__
> - {
> - int skips{};
> - auto rng2{rng};
> - auto rng3{rng};
> - test01<__float128>(rng2, rng3, skips);
> - VERIFY(skips == 0);
> - }
> + test_2p32<__float128>(rng);
> + test_10p6<__float128>(rng);
> + test_2p31m1<__float128>(rng);
> # endif
> #endif
> -
> - { // local RNG, returns [0..999'999)
> - int deviation{}, max{}, rms{}, zero{}, skips{};
> - local_rng<999999ULL> rng2{rng};
> - local_rng<999999ULL> rng3{rng};
> - test02<float>(rng2, rng3, deviation, max, rms, zero, skips);
> -
> - if (std::numeric_limits<float>::is_iec559)
> - {
> - VERIFY(deviation == 8146);
> - VERIFY(max == 250);
> - VERIFY(rms == 1021);
> - VERIFY(zero == 0);
> - }
> - VERIFY(skips == 18);
> - }
> -
> - { // local RNG, returns [0..0x0'7fff'fffe)
> - int deviation{}, max{}, rms{}, zero{}, skips{};
> - local_rng<0x07ffffffeULL> rng2{rng};
> - local_rng<0x07ffffffeULL> rng3{rng};
> - test03<double, 32u>(rng2, rng3, deviation, max, rms, zero, skips);
> -
> - if (std::numeric_limits<double>::is_iec559)
> - {
> - VERIFY(deviation == 7820);
> - VERIFY(max == 240);
> - VERIFY(rms == 950);
> - VERIFY(zero == 0);
> - }
> - VERIFY(skips == 0);
> - }
> }
>
> -int main()
> -{
> - test00();
> -}
> --
> 2.52.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260429/c266bf40/attachment-0001.htm>
More information about the Libstdc++
mailing list