File Coverage

blib/lib/Math/Random/Xoshiro256.pm
Criterion Covered Total %
statement 53 66 80.3
branch 12 26 46.1
condition 1 3 33.3
subroutine 11 11 100.0
pod 4 7 57.1
total 81 113 71.6


line stmt bran cond sub pod time code
1             package Math::Random::Xoshiro256;
2 1     1   85455 use strict;
  1         1  
  1         28  
3 1     1   4 use warnings;
  1         1  
  1         40  
4 1     1   11 use v5.10;
  1         2  
5 1     1   3 use Carp qw(croak);
  1         1  
  1         695  
6              
7             # https://pause.perl.org/pause/query?ACTION=pause_operating_model#3_5_factors_considering_in_the_indexing_phase
8             our $VERSION = '0.1.2';
9              
10             require XSLoader;
11             XSLoader::load('Math::Random::Xoshiro256', $VERSION);
12              
13             sub new {
14 1     1 0 120336 my ($class, $opts) = @_;
15 1         5 my $self = Math::Random::Xoshiro256::_xs_new($class);
16              
17             # Check if the user passed any seeds into the constructor
18 1 50       5 if (exists $opts->{seed}) {
    50          
19 0         0 my $seed = $opts->{seed};
20 0         0 $self->seed($seed);
21             } elsif (exists $opts->{seed4}) {
22 0         0 my @seeds = @$opts->{seeds};
23 0         0 $self->seed4(@seeds);
24             } else {
25 1         5 $self->auto_seed;
26             }
27              
28 1         2 return $self;
29             }
30              
31             sub auto_seed {
32 1     1 0 2 my ($self) = @_;
33              
34             # Get 32 bytes worth of random bytes and build 4x uint64_t seeds from them
35 1         3 my $bytes = os_random_bytes(4 * 8);
36 1         4 my @seeds = unpack('Q4', $bytes);
37              
38 1         6 $self->seed4(@seeds);
39             }
40              
41             # Fetch random bytes from the OS supplied method
42             # /dev/urandom = Linux, Unix, FreeBSD, Mac, Android
43             # Windows requires the Win32::API call to call RtlGenRandom()
44             sub os_random_bytes {
45 1     1 0 4 my $count = shift();
46 1         1 my $ret = "";
47              
48 1 50       29 if ($^O eq 'MSWin32') {
    50          
49 0         0 require Win32::API;
50              
51 0 0       0 state $rand = Win32::API->new(
52             'advapi32',
53             'INT SystemFunction036(PVOID RandomBuffer, ULONG RandomBufferLength)'
54             ) or croak("Could not import SystemFunction036: $^E");
55              
56 0         0 $ret = chr(0) x $count;
57 0 0       0 $rand->Call($ret, $count) or croak("Could not read from csprng: $^E");
58             } elsif (-r "/dev/urandom") {
59 1 50       37 open my $urandom, '<:raw', '/dev/urandom' or croak("Couldn't open /dev/urandom: $!");
60              
61 1 50       14 sysread($urandom, $ret, $count) or croak("Couldn't read from csprng: $!");
62             } else {
63 0         0 croak("Unknown operating system $^O");
64             };
65              
66 1 50       3 if (length($ret) != $count) {
67 0         0 croak("Unable to read $count bytes from OS");
68             }
69              
70 1         3 return $ret;
71             }
72              
73             sub shuffle_array {
74 1     1 1 883 my ($self, @array) = @_;
75              
76             # Make a copy of the array to shuffle
77 1         3 my @shuffled = @array;
78 1         2 my $n = scalar(@shuffled);
79              
80             # Shuffle the array using the Fisher-Yates algorithm
81 1         5 for (my $i = $n - 1; $i > 0; $i--) {
82 8         13 my $j = $self->random_int(0, $i);
83 8 100       16 @shuffled[$i, $j] = @shuffled[$j, $i] if $i != $j;
84             }
85              
86 1         4 return @shuffled;
87             }
88              
89             sub random_elem {
90 1     1 1 395 my ($self, @array) = @_;
91              
92 1 50       3 if (!@array) {
93 0         0 return undef;
94             }
95              
96 1         4 my $idx = $self->random_int(0, scalar(@array) - 1);
97 1         2 my $ret = $array[$idx];
98              
99 1         15 return $ret;
100             }
101              
102             sub random_bytes {
103 2     2 1 406 my ($self, $num) = @_;
104              
105 2 50 33     12 if (!defined($num) || $num <= 0) {
106 0         0 croak("random_bytes: positive number required");
107             }
108              
109             # Get random bytes until we have the desired number
110 2         3 my $bytes = '';
111 2         5 while (length($bytes) < $num) {
112 3         12 my $rand64 = $self->rand64;
113 3         10 $bytes .= pack('Q<', $rand64); # little endian for each 64-bit chunk
114             }
115              
116 2         5 return substr($bytes, 0, $num);
117             }
118              
119             sub random_float {
120 10000     10000 1 29543 my ($self, $non_inclusive) = @_;
121              
122             # Get a random 64-bit integer and convert it to a float in [0,1]
123 10000         14492 my $u64 = $self->rand64;
124 10000         11220 my $top53 = $u64 >> 11;
125              
126 10000         10166 my $ret;
127 10000 50       12184 if ($non_inclusive) {
128 0         0 $ret = $top53 / ((2**53) - 1);
129             } else {
130 10000         11082 $ret = $top53 / (2**53);
131             }
132              
133 10000         15553 return $ret;
134             }
135              
136             1;
137             __END__