File Coverage

blib/lib/Random/Simple.pm
Criterion Covered Total %
statement 100 123 81.3
branch 14 30 46.6
condition 3 5 60.0
subroutine 19 21 90.4
pod 8 14 57.1
total 144 193 74.6


line stmt bran cond sub pod time code
1             package Random::Simple;
2              
3 9     9   970380 use strict;
  9         16  
  9         343  
4 9     9   80 use warnings;
  9         22  
  9         472  
5 9     9   75 use Time::HiRes;
  9         34  
  9         131  
6 9     9   608 use Carp qw(croak);
  9         38  
  9         502  
7 9     9   46 use Config;
  9         16  
  9         353  
8 9     9   115 use v5.10; # For state
  9         26  
9              
10             # https://pause.perl.org/pause/query?ACTION=pause_operating_model#3_5_factors_considering_in_the_indexing_phase
11             our $VERSION = '0.26';
12             our $debug = 0;
13              
14             # Check if the UV (unsigned value) Perl type is 64bit
15             my $has_64bit = ($Config{uvsize} == 8);
16              
17             #############################################################
18              
19             require XSLoader;
20             XSLoader::load();
21              
22             # When this module is loaded (`use`) seed with random bytes from OS
23             seed_with_os_random();
24              
25 9     9   68 use Exporter 'import';
  9         16  
  9         12834  
26             our @EXPORT = qw(random_int random_bytes random_float random_elem shuffle_array rand srand);
27              
28             #############################################################
29              
30             # Throw away the first batch to warm up the PRNG, this is helpful
31             # if a poor seed (lots of zero bits) was chosen
32             sub warmup {
33 9     9 0 17 my ($iter) = @_;
34              
35 9         27 for (my $i = 0; $i < $iter; $i++) {
36 288         601 Random::Simple::_rand64(); # C API
37             }
38             }
39              
40             # Manually seed the PRNG (no warmup)
41             sub seed {
42 11     11 1 67 my ($seed1, $seed2) = @_;
43              
44 11 50       58 if ($debug) {
45 0         0 print "SEEDING MANUALLY ($seed1, $seed2)\n";
46             }
47              
48 11         38 Random::Simple::_seed($seed1, $seed2); # C API
49             }
50              
51             # Fetch random bytes from the OS supplied method
52             # /dev/urandom = Linux, Unix, FreeBSD, Mac, Android
53             # Windows requires the Win32::API call to call RtlGenRandom()
54             sub os_random_bytes {
55 10     10 0 849 my $count = shift();
56 10         20 my $ret = "";
57              
58 10 50       288 if ($^O eq 'MSWin32') {
    50          
59 0         0 require Win32::API;
60              
61 0 0       0 state $rand = Win32::API->new(
62             'advapi32',
63             'INT SystemFunction036(PVOID RandomBuffer, ULONG RandomBufferLength)'
64             ) or croak("Could not import SystemFunction036: $^E");
65              
66 0         0 $ret = chr(0) x $count;
67 0 0       0 $rand->Call($ret, $count) or croak("Could not read from csprng: $^E");
68             } elsif (-r "/dev/urandom") {
69 10 50       434 open my $urandom, '<:raw', '/dev/urandom' or croak("Couldn't open /dev/urandom: $!");
70              
71 10 50       153 sysread($urandom, $ret, $count) or croak("Couldn't read from csprng: $!");
72             } else {
73 0         0 croak("Unknown operating system $^O");
74             };
75              
76 10 50       34 if (length($ret) != $count) {
77 0         0 croak("Unable to read $count bytes from OS");
78             }
79              
80 10         30 return $ret;
81             }
82              
83             # Split a string into an array of smaller length strings
84             sub str_split {
85 9     9 0 23 my ($string, $chunk_size) = @_;
86 9         24 my $num_chunks = length($string) / $chunk_size;
87              
88 9         17 my @ret = ();
89 9         38 for (my $i = 0; $i < $num_chunks; $i++) {
90 36         75 my $str = substr($string, $i * $chunk_size, $chunk_size);
91 36         118 push(@ret, $str);
92             }
93              
94 9         55 return @ret;
95             }
96              
97             # Binary to hex for human readability
98             sub bin2hex {
99 0     0 0 0 my $bytes = shift();
100 0         0 my $ret = (unpack("h* ", $bytes));
101              
102 0         0 return $ret;
103             }
104              
105             # Randomly seed the PRNG and warmup
106             sub seed_with_os_random {
107 9     9 0 18 my ($high, $low, $seed1, $seed2);
108              
109             # PCG needs to be seeded with 2x 64bit unsigned integers
110             # We fetch 16 bytes from the OS to create the two seeds
111             # we need for proper seeding
112              
113 9         23 my $bytes = os_random_bytes(16);
114 9         25 my @parts = str_split($bytes, 4);
115              
116 9 50       31 if (length($bytes) != 16) {
117 0         0 my $size = length($bytes);
118 0         0 die("Did not get enough entropy bytes from OS (got $size bytes)\n");
119             }
120              
121             # Build the first 64bit seed from the random bytes
122             # Cannot use Q because it doesn't exist on 32bit Perls
123 9         40 $high = unpack("L", $parts[0]);
124 9         20 $low = unpack("L", $parts[1]);
125 9         24 $seed1 = ($high << 32) | $low;
126              
127             # Build the second 64bit seed
128 9         18 $high = unpack("L", $parts[2]);
129 9         17 $low = unpack("L", $parts[3]);
130 9         17 $seed2 = ($high << 32) | $low;
131              
132 9 50       27 if ($debug) {
133 0         0 print "RANDOM SEEDS: $seed1 / $seed2\n\n";
134             }
135              
136 9 50 33     34 if ($seed1 == 0 && $seed2 == 0) {
137 0         0 die("ERROR: Seeding from OS failed. Both zero? #91393\n");
138             }
139              
140             # Seed the PRNG with the values we just created
141 9         53 Random::Simple::_seed($seed1, $seed2); # C API
142              
143 9         19 warmup(32);
144             }
145              
146             ######################################################################
147             # Below are the public user callable methods
148             ######################################################################
149              
150             # Get a string of random bytes
151             sub random_bytes {
152 7     7 1 166051 my ($num) = @_;
153              
154 7         20 my $octets_needed = $num / 4;
155              
156 7         15 my $ret = "";
157 7         29 for (my $i = 0; $i < $octets_needed; $i++) {
158 278         505 my $num = Random::Simple::_rand32(); # C API
159              
160             # Convert the integer into a 4 byte string
161 278         714 $ret .= pack("L", $num);
162             }
163              
164 7         23 $ret = substr($ret, 0, $num);
165              
166 7         37 return $ret;
167             }
168              
169             # Get a random non-biased integer in a given range (inclusive)
170             # Note: Range must be no larger than 2^32 - 2
171             sub random_int {
172 100008     100008 1 382211 my ($min, $max) = @_;
173              
174 100008 50       187150 if ($max < $min) { die("Max can't be less than min"); }
  0         0  
175              
176 100008         144914 my $range = $max - $min + 1; # +1 makes it inclusive
177 100008         174006 my $ret = _bounded_rand($range);
178 100008         136491 $ret += $min;
179              
180 100008         170124 return $ret;
181             }
182              
183             # Get a random float between 0 and 1 inclusive
184             sub random_float {
185 10000     10000 1 40159 my $ret = 0;
186              
187 10000 50       16636 if ($has_64bit) {
188 10000         17789 my $num = Random::Simple::_rand64();
189 10000         18542 $ret = Random::Simple::_uint64_to_double($num, 1);
190             } else {
191 0         0 my $num = Random::Simple::_rand32();
192 0         0 $ret = Random::Simple::_uint32_to_float($num, 1);
193             }
194              
195 10000         17311 return $ret;
196             }
197              
198             # Pick a random element from an array
199             sub random_elem {
200 10000     10000 1 58574 my @arr = @_;
201              
202 10000         16303 my $elem_count = scalar(@arr) - 1;
203 10000         17651 my $idx = Random::Simple::random_int(0, $elem_count);
204 10000         17676 my $ret = $arr[$idx];
205              
206 10000         25551 return $ret;
207             }
208              
209             # Use the Fisher-Yates algo to shuffle an array in a non-biased way
210             sub shuffle_array {
211 1     1 1 956 my @array = @_;
212              
213 1         1 my $i = @array;
214 1         4 while ($i--) {
215 8         11 my $j = Random::Simple::random_int(0, $i);
216 8         13 @array[$i, $j] = @array[$j, $i];
217             }
218              
219 1         4 return @array;
220             }
221              
222             sub perl_rand64 {
223 0     0 0 0 my $high = rand() * 4294967295;
224 0         0 my $low = rand() * 4294967295;
225              
226 0         0 my $ret = ($high << 32) | $low;
227              
228 0         0 return $ret;
229             }
230              
231             # Our srand() overrides CORE::srand()
232             sub srand {
233 4     4 1 13 my $seed = $_[0];
234              
235 4 100       9 if (!$seed) {
236 2         69 $seed = int(rand() * 4294967295); # Random 32bit int
237             }
238              
239             # Seed has to be an integer
240 4         6 $seed = int($seed);
241              
242             # Convert one 32bit seed into 2x 64bit seeds
243 4         9 my $seed1 = _hash_mur3($seed); # C API
244 4         7 my $seed2 = _hash_mur3($seed1); # C API
245              
246 4         9 Random::Simple::seed($seed1, $seed2);
247              
248 4         13 return $seed;
249             }
250              
251             # Our rand() overrides CORE::rand()
252             # This is slightly different than random_float because it returns
253             # a number where: 0 <= x < 1
254             #
255             # This prototype is required so we can emulate CORE::rand(@array)
256             sub rand(;$) {
257 30029     30029 1 1513984 my ($mult) = @_;
258              
259 30029   100     76752 $mult ||= 1;
260 30029         43225 my $ret;
261              
262 30029 50       53776 if ($has_64bit) {
263 30029         57328 my $num = Random::Simple::_rand64();
264 30029         58953 $ret = Random::Simple::_uint64_to_double($num, 0);
265             } else {
266 0         0 my $num = Random::Simple::_rand32();
267 0         0 $ret = Random::Simple::_uint32_to_float($num, 0);
268             }
269              
270 30029         49198 $ret *= $mult;
271              
272 30029         59490 return $ret;
273             }
274              
275             #############################################################
276              
277             =encoding utf8
278              
279             =head1 NAME
280              
281             Random::Simple - Generate good random numbers in a user consumable way.
282              
283             =head1 SYNOPSIS
284              
285             use Random::Simple;
286              
287             my $prng = new Random::Simple();
288              
289             my $coin_flip = random_int(1, 2);
290             my $die_roll = random_int(1, 6);
291             my $random_percent = random_float() * 100;
292             my $buffer = random_bytes(8);
293              
294             my @arr = ('red', 'green', 'blue');
295             my $rand_item = random_elem(@arr);
296             my @mixed = shuffle_array(@arr);
297              
298             =head1 DESCRIPTION
299              
300             Perl's internal C function uses C which is an older
301             pseudorandom number generator and may have limitations. C uses
302             PCG which is: modern, simple, well vetted, and fast. Using C
303             will automatically upgrade/override the core C function to use a
304             better PRNG.
305              
306             C is automatically seeded with entropy directly
307             from your OS. On Linux this is C and on Windows it uses
308             RtlGenRandom.
309              
310             When you C we automatically upgrade C and C
311             to use a modern PRNG with better statistical properties. As a bonus you also
312             get a handful of other useful random related methods.
313              
314             =head1 METHODS
315              
316             =over 4
317              
318             =item B
319              
320             returns a non-biased integer between C<$min> and C<$max> (inclusive). Range must be no larger than 2**32 - 2.
321              
322             =item B
323              
324             returns a random floating point value between 0 and 1 (inclusive).
325              
326             =item B
327              
328             returns a string of random bytes with length of C<$number>.
329              
330             =item B
331              
332             returns a random element from C<@array>.
333              
334             =item B
335              
336             returns an array that has been randomized using the Fisher-Yates alorgithm.
337              
338             =item B
339              
340             emulates C using a better PRNG.
341              
342             =item B
343              
344             emulates C using a better PRNG.
345              
346             =item B
347              
348             Seed the PRNG with two unsigned 64bit integers for predictable and repeatable
349             random numbers. C will automatically seed itself from your
350             operating system's randomness if not manually seeded. Manual seeding should
351             only be used in specific cases where you need repeatable or testable
352             randomness.
353              
354             =back
355              
356             =head1 CAVEATS
357              
358             PCG uses two 64bit unsigned integers for seeding. High quality seeds are needed
359             to generate good random numbers. C automatically generates high
360             quality seeds by reading random bytes from your operating system and converting
361             appropriately.
362              
363             If you manually seed C, make sure you use good seeds that
364             are mostly non-zero. The larger the number the better seed it will make. A good
365             seed is a decimal number with 18 or 19 digits.
366              
367             =head1 BUGS
368              
369             Submit issues on Github: L
370              
371             =head1 SEE ALSO
372              
373             =over
374              
375             =item *
376             L
377              
378             =item *
379             L
380              
381             =item *
382             L
383              
384             =item *
385             L
386              
387             =back
388              
389             =head1 AUTHOR
390              
391             Scott Baker - L
392              
393             =cut
394              
395             1;