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)); }
{ 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;
}
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)
+{
+ if (!std::numeric_limits<T>::is_iec559)
+ return;
+
+ 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);
+
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);
}
}
-// 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
-#if __LDBL_MANT_DIG__ != 106 // disable this for IBM double double
- int skips{};
- auto rng2{rng};
- auto rng3{rng};
- test01<long double>(rng2, rng3, skips);
- VERIFY(skips == 0);
-#endif
- }
#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();
-}