File Coverage

blib/lib/Math/ModInt/ChineseRemainder.pm
Criterion Covered Total %
statement 80 80 100.0
branch 20 20 100.0
condition 6 6 100.0
subroutine 15 15 100.0
pod 6 6 100.0
total 127 127 100.0


line stmt bran cond sub pod time code
1             package Math::ModInt::ChineseRemainder;
2              
3 2     2   1660 use 5.006;
  2         7  
4 2     2   11 use strict;
  2         4  
  2         49  
5 2     2   10 use warnings;
  2         12  
  2         63  
6 2     2   11 use Math::ModInt qw(mod);
  2         7  
  2         110  
7 2     2   13 use overload ();
  2         15  
  2         169  
8              
9             # ----- class data -----
10              
11             BEGIN {
12 2     2   14 require Exporter;
13 2         41 our @ISA = qw(Exporter);
14 2         8 our @EXPORT_OK = qw(cr_combine cr_extract);
15 2         66 our $VERSION = '0.013';
16             }
17              
18 2     2   12 use constant _INITIAL_CACHE_SIZE => 1024;
  2         11  
  2         1718  
19              
20             my $cache_size = _INITIAL_CACHE_SIZE;
21             my %param_cache = (); # memoizing param arrayrefs, key "m:n"
22             my @param_fifo = (); # list of up to $cache_size keys
23              
24             # parameter arrayref:
25             # [
26             # ModInt factor for greater modulus,
27             # ModInt factor for smaller modulus,
28             # greatest common divisor of moduli
29             # ]
30              
31             # ----- private subroutines -----
32              
33             # extended euclidian algorithm to find modulus-specific parameters
34             # moduli must be in descending order
35             sub _calculate_params {
36 14     14   29 my ($mod_g, $mod_s) = @_;
37 14         26 my ($g, $s) = ($mod_g, $mod_s);
38 14         30 my ($gg, $gs, $sg, $ss) = (1, 0, 0, 1);
39 14         29 while ($s != 0) {
40 27         4209 my $m = $g % $s;
41 27         1273 my $d = ($g - $m) / $s;
42 27         2758 ($g, $gg, $gs, $s, $sg, $ss) =
43             ($s, $sg, $ss, $m, $gg - $d * $sg, $gs - $d * $ss);
44             }
45 14         1913 $ss = abs $ss;
46 14         113 $sg = abs $sg;
47 14         100 my $lcm = $mod_g * $sg;
48 14         278 my $coeff_g = mod($gs * $sg, $lcm);
49 14         647 my $coeff_s = $coeff_g->new($gg * $ss);
50 14         606 return ($coeff_g, $coeff_s, $g);
51             }
52              
53             # fetch memoized params or calculate them
54             # moduli must be in descending order
55             sub _get_params {
56 20     20   41 my ($mod_g, $mod_s) = @_;
57 20         30 my @params;
58 20 100       44 if ($cache_size) {
59 19         50 my $key = "$mod_g:$mod_s";
60 19 100       223 if (exists $param_cache{$key}) {
61 6         8 @params = @{$param_cache{$key}};
  6         21  
62             }
63             else {
64 13         29 @params = _calculate_params($mod_g, $mod_s);
65 13 100       33 if (@param_fifo >= $cache_size) {
66 1         5 delete $param_cache{shift @param_fifo};
67             }
68 13         26 push @param_fifo, $key;
69 13         36 $param_cache{$key} = \@params;
70             }
71             }
72             else {
73 1         4 @params = _calculate_params($mod_g, $mod_s);
74             }
75 20         70 return @params;
76             }
77              
78             # ----- public subroutines -----
79              
80             sub cr_combine {
81 15     15 1 490 foreach my $arg (@_) {
82 35 100       83 return $arg if $arg->is_undefined;
83             }
84 14         49 my @these = sort { $a->modulus <=> $b->modulus } @_;
  30         75  
85 14 100       37 return mod(0, 1) if !@these;
86 13         21 my $this = pop @these;
87 13         32 while (@these) {
88 20         970 my $that = pop @these;
89 20         49 my ($coeff_this, $coeff_that, $gcd) =
90             _get_params($this->modulus, $that->modulus);
91 20 100 100     71 if ($gcd != 1 && $this->residue % $gcd != $that->residue % $gcd) {
92 4         12 return Math::ModInt->undefined;
93             }
94             $this =
95 16         775 $coeff_this * $coeff_this->new($this->residue) +
96             $coeff_that * $coeff_that->new($that->residue);
97             }
98 9         467 return $this;
99             }
100              
101             sub cr_extract {
102 16     16 1 264 my ($this, $desired_modulus) = @_;
103 16 100       57 return Math::ModInt->undefined if $this->is_undefined;
104 15 100       45 if (0 != $this->modulus % $desired_modulus) {
105 1         3 return Math::ModInt->undefined;
106             }
107 14         2173 my $residue = $this->residue;
108             # make sure residue does not exceed the precision of modulus
109 14 100 100     61 if (!ref $desired_modulus && ref $residue) {
110 5         16 my $as_number = overload::Method($residue, '0+');
111 5         1793 $residue = ($residue % $desired_modulus)->$as_number;
112             }
113 14         1169 return mod($residue, $desired_modulus);
114             }
115              
116             sub cache_level {
117 13     13 1 584 return scalar @param_fifo;
118             }
119              
120             sub cache_flush {
121 2     2 1 110 %param_cache = ();
122 2         5 @param_fifo = ();
123 2         5 return 0;
124             }
125              
126             sub cache_size {
127 8     8 1 255 return $cache_size;
128             }
129              
130             sub cache_resize {
131 7     7 1 489 my $size = pop;
132 7         12 $cache_size = $size;
133 7 100       19 if ($cache_size < @param_fifo) {
134 2         13 delete @param_cache{splice @param_fifo, 0, @param_fifo - $cache_size};
135             }
136 7         24 return $cache_size;
137             }
138              
139             1;
140              
141             __END__