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             # Copyright (c) 2010-2019 Martin Becker, Blaubeuren.
2             # This package is free software; you can distribute it and/or modify it
3             # under the terms of the Artistic License 2.0 (see LICENSE file).
4              
5             package Math::ModInt::ChineseRemainder;
6              
7 2     2   1136 use 5.006;
  2         7  
8 2     2   11 use strict;
  2         4  
  2         40  
9 2     2   8 use warnings;
  2         4  
  2         53  
10 2     2   9 use Math::ModInt qw(mod);
  2         4  
  2         88  
11 2     2   9 use overload ();
  2         16  
  2         146  
12              
13             # ----- class data -----
14              
15             BEGIN {
16 2     2   11 require Exporter;
17 2         43 our @ISA = qw(Exporter);
18 2         7 our @EXPORT_OK = qw(cr_combine cr_extract);
19 2         62 our $VERSION = '0.012';
20             }
21              
22 2     2   11 use constant _INITIAL_CACHE_SIZE => 1024;
  2         15  
  2         1611  
23              
24             my $cache_size = _INITIAL_CACHE_SIZE;
25             my %param_cache = (); # memoizing param arrayrefs, key "m:n"
26             my @param_fifo = (); # list of up to $cache_size keys
27              
28             # parameter arrayref:
29             # [
30             # ModInt factor for greater modulus,
31             # ModInt factor for smaller modulus,
32             # greatest common divisor of moduli
33             # ]
34              
35             # ----- private subroutines -----
36              
37             # extended euclidian algorithm to find modulus-specific parameters
38             # moduli must be in descending order
39             sub _calculate_params {
40 14     14   27 my ($mod_g, $mod_s) = @_;
41 14         27 my ($g, $s) = ($mod_g, $mod_s);
42 14         28 my ($gg, $gs, $sg, $ss) = (1, 0, 0, 1);
43 14         29 while ($s != 0) {
44 27         4329 my $m = $g % $s;
45 27         1291 my $d = ($g - $m) / $s;
46 27         2766 ($g, $gg, $gs, $s, $sg, $ss) =
47             ($s, $sg, $ss, $m, $gg - $d * $sg, $gs - $d * $ss);
48             }
49 14         1891 $ss = abs $ss;
50 14         108 $sg = abs $sg;
51 14         101 my $lcm = $mod_g * $sg;
52 14         273 my $coeff_g = mod($gs * $sg, $lcm);
53 14         690 my $coeff_s = $coeff_g->new($gg * $ss);
54 14         614 return ($coeff_g, $coeff_s, $g);
55             }
56              
57             # fetch memoized params or calculate them
58             # moduli must be in descending order
59             sub _get_params {
60 20     20   40 my ($mod_g, $mod_s) = @_;
61 20         24 my @params;
62 20 100       40 if ($cache_size) {
63 19         46 my $key = "$mod_g:$mod_s";
64 19 100       239 if (exists $param_cache{$key}) {
65 6         12 @params = @{$param_cache{$key}};
  6         15  
66             }
67             else {
68 13         29 @params = _calculate_params($mod_g, $mod_s);
69 13 100       32 if (@param_fifo >= $cache_size) {
70 1         4 delete $param_cache{shift @param_fifo};
71             }
72 13         26 push @param_fifo, $key;
73 13         43 $param_cache{$key} = \@params;
74             }
75             }
76             else {
77 1         3 @params = _calculate_params($mod_g, $mod_s);
78             }
79 20         56 return @params;
80             }
81              
82             # ----- public subroutines -----
83              
84             sub cr_combine {
85 15     15 1 402 foreach my $arg (@_) {
86 35 100       83 return $arg if $arg->is_undefined;
87             }
88 14         49 my @these = sort { $a->modulus <=> $b->modulus } @_;
  30         59  
89 14 100       37 return mod(0, 1) if !@these;
90 13         22 my $this = pop @these;
91 13         29 while (@these) {
92 20         999 my $that = pop @these;
93 20         80 my ($coeff_this, $coeff_that, $gcd) =
94             _get_params($this->modulus, $that->modulus);
95 20 100 100     66 if ($gcd != 1 && $this->residue % $gcd != $that->residue % $gcd) {
96 4         12 return Math::ModInt->undefined;
97             }
98             $this =
99 16         784 $coeff_this * $coeff_this->new($this->residue) +
100             $coeff_that * $coeff_that->new($that->residue);
101             }
102 9         459 return $this;
103             }
104              
105             sub cr_extract {
106 16     16 1 240 my ($this, $desired_modulus) = @_;
107 16 100       56 return Math::ModInt->undefined if $this->is_undefined;
108 15 100       49 if (0 != $this->modulus % $desired_modulus) {
109 1         5 return Math::ModInt->undefined;
110             }
111 14         2052 my $residue = $this->residue;
112             # make sure residue does not exceed the precision of modulus
113 14 100 100     59 if (!ref $desired_modulus && ref $residue) {
114 5         12 my $as_number = overload::Method($residue, '0+');
115 5         1484 $residue = ($residue % $desired_modulus)->$as_number;
116             }
117 14         1234 return mod($residue, $desired_modulus);
118             }
119              
120             sub cache_level {
121 13     13 1 612 return scalar @param_fifo;
122             }
123              
124             sub cache_flush {
125 2     2 1 90 %param_cache = ();
126 2         4 @param_fifo = ();
127 2         6 return 0;
128             }
129              
130             sub cache_size {
131 8     8 1 217 return $cache_size;
132             }
133              
134             sub cache_resize {
135 7     7 1 528 my $size = pop;
136 7         10 $cache_size = $size;
137 7 100       20 if ($cache_size < @param_fifo) {
138 2         13 delete @param_cache{splice @param_fifo, 0, @param_fifo - $cache_size};
139             }
140 7         14 return $cache_size;
141             }
142              
143             1;
144              
145             __END__