Jakub Wilk is now a Perl author.
[perl.git] / t / op / rand.t
1 #!./perl
2
3 # From Tom Phoenix <rootbeer@teleport.com> 22 Feb 1997
4 # Based upon a test script by kgb@ast.cam.ac.uk (Karl Glazebrook)
5
6 # Looking for the hints? You're in the right place. 
7 # The hints are near each test, so search for "TEST #", where
8 # the pound sign is replaced by the number of the test.
9
10 # I'd like to include some more robust tests, but anything
11 # too subtle to be detected here would require a time-consuming
12 # test. Also, of course, we're here to detect only flaws in Perl;
13 # if there are flaws in the underlying system rand, that's not
14 # our responsibility. But if you want better tests, see
15 # The Art of Computer Programming, Donald E. Knuth, volume 2,
16 # chapter 3. ISBN 0-201-03822-6 (v. 2)
17
18 BEGIN {
19     chdir "t" if -d "t";
20     require "./test.pl";
21     set_up_inc( qw(. ../lib) );
22 }
23
24 use strict;
25 use Config;
26
27 my $reps = 100_000;     # How many times to try rand each time.
28                         # May be changed, but should be over 500.
29                         # The more the better! (But slower.)
30
31 my $bits = 8;  # how many significant bits we check on each random number
32 my $nslots = (1<< $bits); # how many different numbers
33
34 plan(tests => 7 + $nslots);
35
36 # First, let's see whether randbits is set right and that rand() returns
37 # an even distribution of values
38 {
39     my $sum;
40     my @slots = (0) x $nslots;
41     my $prob = 1/$nslots;     # probability of a particular slot being
42                               # on a particular iteration
43
44     # We are going to generate $reps random numbers, each in the range
45     # 0..$nslots-1. They should be evenly distributed. We use @slots to
46     # count the number of occurrences of each number. For each count, we
47     # check that it is in the range we expect. For example for reps =
48     # 100_000 and using 8 bits, we expect each count to be around
49     # 100_000/256 = 390. How much around it we tolerate depends on the
50     # standard deviation, and how many deviations we allow. If we allow
51     # 6-sigmas, then that means that in only 1 run in 506e6 will be get a
52     # failure by chance, assuming a fair random number generator. Given
53     # that we test each slot, the overall chance of a false negative in
54     # this test script is about 1 in 2e6, assuming 256 slots.
55     #
56     # the actual count in a slot should follow a binomial distribution
57     # (e.g. rolling 18 dice, we 'expect' to see 3 sixes, but there's
58     # actually a 24% chance of 3, a 20% change of 2 or 4, a 12%
59     # chance of 1 or 5, and a 4% chance of 0 or 6 of them).
60     #
61     # This makes it easy to calculate the expected mean a standard
62     # deviation; see
63     #     https://en.wikipedia.org/wiki/Binomial_distribution#Variance
64
65     my $mean   = $reps * $prob;
66     my $stddev = sqrt($reps * $prob * (1 - $prob));
67     my $sigma6 = $stddev * 6.0; # very unlikely to be outside that range
68     my $min = $mean - $sigma6;
69     my $max = $mean + $sigma6;
70
71     note("reps=$reps; slots=$nslots; min=$min mean=$mean max=$max");
72
73     for (1..$reps) {
74         my $n = rand(1);
75         if ($n < 0.0 or $n >= 1.0) {
76             diag(<<EOM);
77 WHOA THERE!  \$Config{drand01} is set to '$Config{drand01}',
78 but that apparently produces values ($n) < 0.0 or >= 1.0.
79 Make sure \$Config{drand01} is a valid expression in the
80 C-language, and produces values in the range [0.0,1.0).
81
82 I give up.
83 EOM
84             exit;
85         }
86         $slots[int($n * $nslots)]++;
87     }
88
89     for my $i (0..$nslots - 1) {
90         # this test should randomly fail very rarely. If it fails
91         # for you, try re-running this test script a few more times;
92         # if it goes away, it was likely a random (ha ha!) glitch.
93         # If you keep seeing failures, it means your random number
94         # generator is producing a very uneven spread of values.
95         ok($slots[$i] >= $min && $slots[$i] <= $max, "checking slot $i")
96             or diag("slot $i; count $slots[$i] outside expected range $min..$max");
97     }
98 }
99
100
101 # Now, let's see whether rand accepts its argument
102 {
103     my($max, $min);
104     $max = $min = rand(100);
105     for (1..$reps) {
106         my $n = rand(100);
107         $max = $n if $n > $max;
108         $min = $n if $n < $min;
109     }
110
111     # This test checks to see that rand(100) really falls 
112     # within the range 0 - 100, and that the numbers produced
113     # have a reasonably-large range among them.
114     #
115     cmp_ok($min,        '>=',  0, "rand(100) >= 0");
116     cmp_ok($max,        '<', 100, "rand(100) < 100");
117     cmp_ok($max - $min, '>=', 65, "rand(100) in 65 range");
118
119
120     # This test checks that rand without an argument
121     # is equivalent to rand(1).
122     #
123     $_ = 12345;         # Just for fun.
124     srand 12345;
125     my $r = rand;
126     srand 12345;
127     is(rand(1),  $r,  'rand() without args is rand(1)');
128
129
130     # This checks that rand without an argument is not
131     # rand($_). (In case somebody got overzealous.)
132     # 
133     cmp_ok($r, '<', 1,   'rand() without args is under 1');
134 }
135
136 { # [perl #115928] use a standard rand() implementation
137     srand(1);
138     is(int rand(1000), 41, "our own implementation behaves consistently");
139     is(int rand(1000), 454, "and still consistently");
140 }