[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