[PATCH] libstdc++: Rework P0952 generate_cannonical tests.
Jonathan Wakely
jwakely@redhat.com
Wed Apr 29 14:50:23 GMT 2026
On Wed, 29 Apr 2026 at 15:25, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> 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
OK for trunk with that change, thanks
>>
>> +
>> 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
>>
More information about the Libstdc++
mailing list