File Coverage

blib/lib/Random/Simple.pm
Criterion Covered Total %
statement 114 143 79.7
branch 22 44 50.0
condition 4 8 50.0
subroutine 19 21 90.4
pod 8 14 57.1
total 167 230 72.6


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