File Coverage

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


line stmt bran cond sub pod time code
1             # Copyright (c) 2009-2015 Martin Becker. All rights reserved.
2             # This package is free software; you can redistribute it and/or modify it
3             # under the same terms as Perl itself.
4             #
5             # $Id: ChineseRemainder.pm 60 2015-05-18 08:47:12Z demetri $
6              
7             package Math::ModInt::ChineseRemainder;
8              
9 2     2   1478 use 5.006;
  2         7  
  2         84  
10 2     2   9 use strict;
  2         2  
  2         61  
11 2     2   7 use warnings;
  2         2  
  2         57  
12 2     2   7 use Math::ModInt qw(mod);
  2         2  
  2         114  
13 2     2   8 use overload ();
  2         8  
  2         163  
14              
15             # ----- class data -----
16              
17             BEGIN {
18 2     2   49 require Exporter;
19 2         31 our @ISA = qw(Exporter);
20 2         5 our @EXPORT_OK = qw(cr_combine cr_extract);
21 2         90 our $VERSION = '0.011';
22             }
23              
24 2     2   12 use constant _INITIAL_CACHE_SIZE => 1024;
  2         3  
  2         1694  
25              
26             my $cache_size = _INITIAL_CACHE_SIZE;
27             my %param_cache = (); # memoizing param arrayrefs, key "m:n"
28             my @param_fifo = (); # list of up to $cache_size keys
29              
30             # parameter arrayref:
31             # [
32             # ModInt factor for greater modulus,
33             # ModInt factor for smaller modulus,
34             # greatest common divisor of moduli
35             # ]
36              
37             # ----- private subroutines -----
38              
39             # extended euclidian algorithm to find modulus-specific parameters
40             # moduli must be in descending order
41             sub _calculate_params {
42 14     14   20 my ($mod_g, $mod_s) = @_;
43 14         21 my ($g, $s) = ($mod_g, $mod_s);
44 14         21 my ($gg, $gs, $sg, $ss) = (1, 0, 0, 1);
45 14         33 while ($s != 0) {
46 27         2450 my $m = $g % $s;
47 27         671 my $d = ($g - $m) / $s;
48 27         1273 ($g, $gg, $gs, $s, $sg, $ss) =
49             ($s, $sg, $ss, $m, $gg - $d * $sg, $gs - $d * $ss);
50             }
51 14         1081 $ss = abs $ss;
52 14         73 $sg = abs $sg;
53 14         56 my $lcm = $mod_g * $sg;
54 14         175 my $coeff_g = mod($gs * $sg, $lcm);
55 14         381 my $coeff_s = $coeff_g->new($gg * $ss);
56 14         369 return ($coeff_g, $coeff_s, $g);
57             }
58              
59             # fetch memoized params or calculate them
60             # moduli must be in descending order
61             sub _get_params {
62 20     20   30 my ($mod_g, $mod_s) = @_;
63 20         18 my @params;
64 20 100       40 if ($cache_size) {
65 19         45 my $key = "$mod_g:$mod_s";
66 19 100       182 if (exists $param_cache{$key}) {
67 6         8 @params = @{$param_cache{$key}};
  6         16  
68             }
69             else {
70 13         28 @params = _calculate_params($mod_g, $mod_s);
71 13 100       43 if (@param_fifo >= $cache_size) {
72 1         5 delete $param_cache{shift @param_fifo};
73             }
74 13         20 push @param_fifo, $key;
75 13         41 $param_cache{$key} = \@params;
76             }
77             }
78             else {
79 1         3 @params = _calculate_params($mod_g, $mod_s);
80             }
81 20         48 return @params;
82             }
83              
84             # ----- public subroutines -----
85              
86             sub cr_combine {
87 15     15 1 905 foreach my $arg (@_) {
88 35 100       81 return $arg if $arg->is_undefined;
89             }
90 14         56 my @these = sort { $a->modulus <=> $b->modulus } @_;
  30         55  
91 14 100       38 return mod(0, 1) if !@these;
92 13         22 my $this = pop @these;
93 13         32 while (@these) {
94 20         542 my $that = pop @these;
95 20         78 my ($coeff_this, $coeff_that, $gcd) =
96             _get_params($this->modulus, $that->modulus);
97 20 100 100     77 if ($gcd != 1 && $this->residue % $gcd != $that->residue % $gcd) {
98 4         16 return Math::ModInt->undefined;
99             }
100             $this =
101 16         441 $coeff_this * $coeff_this->new($this->residue) +
102             $coeff_that * $coeff_that->new($that->residue);
103             }
104 9         266 return $this;
105             }
106              
107             sub cr_extract {
108 16     16 1 561 my ($this, $desired_modulus) = @_;
109 16 100       57 return Math::ModInt->undefined if $this->is_undefined;
110 15 100       42 if (0 != $this->modulus % $desired_modulus) {
111 1         4 return Math::ModInt->undefined;
112             }
113 14         1167 my $residue = $this->residue;
114             # make sure residue does not exceed the precision of modulus
115 14 100 100     60 if (!ref $desired_modulus && ref $residue) {
116 5         13 my $as_number = overload::Method($residue, '0+');
117 5         1327 $residue = ($residue % $desired_modulus)->$as_number;
118             }
119 14         691 return mod($residue, $desired_modulus);
120             }
121              
122             sub cache_level {
123 13     13 1 2300 return scalar @param_fifo;
124             }
125              
126             sub cache_flush {
127 2     2 1 359 %param_cache = ();
128 2         5 @param_fifo = ();
129 2         6 return 0;
130             }
131              
132             sub cache_size {
133 8     8 1 860 return $cache_size;
134             }
135              
136             sub cache_resize {
137 7     7 1 1497 my $size = pop;
138 7         10 $cache_size = $size;
139 7 100       22 if ($cache_size < @param_fifo) {
140 2         19 delete @param_cache{splice @param_fifo, 0, @param_fifo - $cache_size};
141             }
142 7         18 return $cache_size;
143             }
144              
145             1;
146              
147             __END__