[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