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
18BEGIN {
19    chdir "t" if -d "t";
20    @INC = qw(. ../lib);
21}
22
23use strict;
24use Config;
25
26require "test.pl";
27plan(tests => 8);
28
29
30my $reps = 10000;	# How many times to try rand each time.
31			# May be changed, but should be over 500.
32			# The more the better! (But slower.)
33
34sub bits ($) {
35    # Takes a small integer and returns the number of one-bits in it.
36    my $total;
37    my $bits = sprintf "%o", $_[0];
38    while (length $bits) {
39	$total += (0,1,1,2,1,2,2,3)[chop $bits];	# Oct to bits
40    }
41    $total;
42}
43
44# First, let's see whether randbits is set right
45{
46    my($max, $min, $sum);	# Characteristics of rand
47    my($off, $shouldbe);	# Problems with randbits
48    my($dev, $bits);		# Number of one bits
49    my $randbits = $Config{randbits};
50    $max = $min = rand(1);
51    for (1..$reps) {
52	my $n = rand(1);
53	if ($n < 0.0 or $n >= 1.0) {
54	    print <<EOM;
55# WHOA THERE!  \$Config{drand01} is set to '$Config{drand01}',
56# but that apparently produces values < 0.0 or >= 1.0.
57# Make sure \$Config{drand01} is a valid expression in the
58# C-language, and produces values in the range [0.0,1.0).
59#
60# I give up.
61EOM
62	    exit;
63	}
64	$sum += $n;
65	$bits += bits($n * 256);	# Don't be greedy; 8 is enough
66		    # It's too many if randbits is less than 8!
67		    # But that should never be the case... I hope.
68		    # Note: If you change this, you must adapt the
69		    # formula for absolute standard deviation, below.
70	$max = $n if $n > $max;
71	$min = $n if $n < $min;
72    }
73
74
75    # This test checks for one of Perl's most frequent
76    # mis-configurations. Your system's documentation
77    # for rand(2) should tell you what value you need
78    # for randbits. Usually the diagnostic message
79    # has the right value as well. Just fix it and
80    # recompile, and you'll usually be fine. (The main 
81    # reason that the diagnostic message might get the
82    # wrong value is that Config.pm is incorrect.)
83    #
84    unless (ok( !$max <= 0 or $max >= (2 ** $randbits))) {# Just in case...
85	print <<DIAG;
86# max=[$max] min=[$min]
87# This perl was compiled with randbits=$randbits
88# which is _way_ off. Or maybe your system rand is broken,
89# or your C compiler can't multiply, or maybe Martians
90# have taken over your computer. For starters, see about
91# trying a better value for randbits, probably smaller.
92DIAG
93
94	# If that isn't the problem, we'll have
95	# to put d_martians into Config.pm 
96	print "# Skipping remaining tests until randbits is fixed.\n";
97	exit;
98    }
99
100    $off = log($max) / log(2);			# log2
101    $off = int($off) + ($off > 0);		# Next more positive int
102    unless (is( $off, 0 )) {
103	$shouldbe = $Config{randbits} + $off;
104	print "# max=[$max] min=[$min]\n";
105	print "# This perl was compiled with randbits=$randbits on $^O.\n";
106	print "# Consider using randbits=$shouldbe instead.\n";
107	# And skip the remaining tests; they would be pointless now.
108	print "# Skipping remaining tests until randbits is fixed.\n";
109	exit;
110    }
111
112
113    # This should always be true: 0 <= rand(1) < 1
114    # If this test is failing, something is seriously wrong,
115    # either in perl or your system's rand function.
116    #
117    unless (ok( !($min < 0 or $max >= 1) )) {	# Slightly redundant...
118	print "# min too low\n" if $min < 0;
119	print "# max too high\n" if $max >= 1;
120    }
121
122
123    # This is just a crude test. The average number produced
124    # by rand should be about one-half. But once in a while
125    # it will be relatively far away. Note: This test will
126    # occasionally fail on a perfectly good system!
127    # See the hints for test 4 to see why.
128    #
129    $sum /= $reps;
130    unless (ok( !($sum < 0.4 or $sum > 0.6) )) {
131	print "# Average random number is far from 0.5\n";
132    }
133
134
135    #   NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
136    # This test will fail .1% of the time on a normal system.
137    #				also
138    # This test asks you to see these hints 100% of the time!
139    #   NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
140    #
141    # There is probably no reason to be alarmed that
142    # something is wrong with your rand function. But,
143    # if you're curious or if you can't help being 
144    # alarmed, keep reading.
145    #
146    # This is a less-crude test than test 3. But it has
147    # the same basic flaw: Unusually distributed random
148    # values should occasionally appear in every good
149    # random number sequence. (If you flip a fair coin
150    # twenty times every day, you'll see it land all
151    # heads about one time in a million days, on the
152    # average. That might alarm you if you saw it happen
153    # on the first day!)
154    #
155    # So, if this test failed on you once, run it a dozen
156    # times. If it keeps failing, it's likely that your
157    # rand is bogus. If it keeps passing, it's likely
158    # that the one failure was bogus. If it's a mix,
159    # read on to see about how to interpret the tests.
160    #
161    # The number printed in square brackets is the
162    # standard deviation, a statistical measure
163    # of how unusual rand's behavior seemed. It should
164    # fall in these ranges with these *approximate*
165    # probabilities:
166    #
167    #		under 1		68.26% of the time
168    #		1-2		27.18% of the time
169    #		2-3		 4.30% of the time
170    #		over 3		 0.26% of the time
171    #
172    # If the numbers you see are not scattered approximately
173    # (not exactly!) like that table, check with your vendor
174    # to find out what's wrong with your rand. Or with this
175    # algorithm. :-)
176    #
177    # Calculating absoulute standard deviation for number of bits set
178    # (eight bits per rep)
179    $dev = abs ($bits - $reps * 4) / sqrt($reps * 2);
180
181    ok( $dev < 3.3 );
182
183    if ($dev < 1.96) {
184	print "# Your rand seems fine. If this test failed\n";
185	print "# previously, you may want to run it again.\n";
186    } elsif ($dev < 2.575) {
187	print "# This is ok, but suspicious. But it will happen\n";
188	print "# one time out of 25, more or less.\n";
189	print "# You should run this test again to be sure.\n";
190    } elsif ($dev < 3.3) {
191	print "# This is very suspicious. It will happen only\n";
192	print "# about one time out of 100, more or less.\n";
193	print "# You should run this test again to be sure.\n";
194    } elsif ($dev < 3.9) {
195	print "# This is VERY suspicious. It will happen only\n";
196	print "# about one time out of 1000, more or less.\n";
197	print "# You should run this test again to be sure.\n";
198    } else {
199	print "# This is VERY VERY suspicious.\n";
200	print "# Your rand seems to be bogus.\n";
201    }
202    print "#\n# If you are having random number troubles,\n";
203    print "# see the hints within the test script for more\n";
204    printf "# information on why this might fail. [ %.3f ]\n", $dev;
205}
206
207
208# Now, let's see whether rand accepts its argument
209{
210    my($max, $min);
211    $max = $min = rand(100);
212    for (1..$reps) {
213	my $n = rand(100);
214	$max = $n if $n > $max;
215	$min = $n if $n < $min;
216    }
217
218    # This test checks to see that rand(100) really falls 
219    # within the range 0 - 100, and that the numbers produced
220    # have a reasonably-large range among them.
221    #
222    unless ( ok( !($min < 0 or $max >= 100 or ($max - $min) < 65) ) ) {
223	print "# min too low\n" if $min < 0;
224	print "# max too high\n" if $max >= 100;
225	print "# range too narrow\n" if ($max - $min) < 65;
226    }
227
228
229    # This test checks that rand without an argument
230    # is equivalent to rand(1).
231    #
232    $_ = 12345;		# Just for fun.
233    srand 12345;
234    my $r = rand;
235    srand 12345;
236    is(rand(1),  $r,  'rand() without args is rand(1)');
237
238
239    # This checks that rand without an argument is not
240    # rand($_). (In case somebody got overzealous.)
241    # 
242    ok($r < 1,        'rand() without args is under 1');
243}
244
245