line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# |
2
|
|
|
|
|
|
|
# This file is part of Math-Pell |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# This software is copyright (c) 2010 by Stefan Petrea. |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# This is free software; you can redistribute it and/or modify it under |
7
|
|
|
|
|
|
|
# the same terms as the Perl 5 programming language system itself. |
8
|
|
|
|
|
|
|
# |
9
|
1
|
|
|
1
|
|
49141
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
42
|
|
10
|
1
|
|
|
1
|
|
7
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
60
|
|
11
|
|
|
|
|
|
|
package Math::Pell; |
12
|
|
|
|
|
|
|
our $VERSION = '0.6'; |
13
|
1
|
|
|
1
|
|
2837
|
use Moose; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
#use Math::Counting; |
15
|
|
|
|
|
|
|
use Math::BigInt; |
16
|
|
|
|
|
|
|
use POSIX qw(ceil floor); |
17
|
|
|
|
|
|
|
use Carp qw/confess/; |
18
|
|
|
|
|
|
|
use List::AllUtils qw/any/; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=pod |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 NAME |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Math::Pell - Solution for Pell equations |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 VERSION |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
version 0.6 |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 DESCRIPTION |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
This module solves the Pell equation by finding a minimal solution and making a method available for |
34
|
|
|
|
|
|
|
generating all solutions to the Pell equation: |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
x^2 - Dy^2 = 1 |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
Of particular interest are non-trivial solutions to this equation. |
39
|
|
|
|
|
|
|
The numbers x and y are integers. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=head1 SYNOPSIS |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Finding the first 5 solutions of x^2 - 7y^2 = 1 |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
use Math::Pell; |
48
|
|
|
|
|
|
|
my $p = Math::Pell->new({D=>7});# equation is x^2 - Dy^2 = 1 where x,y are integers |
49
|
|
|
|
|
|
|
$p->find_minimal_sol; |
50
|
|
|
|
|
|
|
printf "(%d,%d)\n",$p->nth_solution($_) |
51
|
|
|
|
|
|
|
for 1..5; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
(8,3) |
55
|
|
|
|
|
|
|
(127,48) |
56
|
|
|
|
|
|
|
(2024,765) |
57
|
|
|
|
|
|
|
(32257,12192) |
58
|
|
|
|
|
|
|
(514088,194307) |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=head1 SAMPLE TEST OUTPUT |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sqrt[13]= [3,1,1,1,1,6] |
63
|
|
|
|
|
|
|
n= 2 p=4 q=1 p/q = 4 |
64
|
|
|
|
|
|
|
n= 3 p=7 q=2 p/q = 3 |
65
|
|
|
|
|
|
|
n= 4 p=11 q=3 p/q = 3 |
66
|
|
|
|
|
|
|
n= 5 p=18 q=5 p/q = 3 |
67
|
|
|
|
|
|
|
n= 6 p=119 q=33 p/q = 3 |
68
|
|
|
|
|
|
|
n= 7 p=137 q=38 p/q = 3 |
69
|
|
|
|
|
|
|
n= 8 p=256 q=71 p/q = 3 |
70
|
|
|
|
|
|
|
n= 9 p=393 q=109 p/q = 3 |
71
|
|
|
|
|
|
|
n= 10 p=649 q=180 p/q = 3 |
72
|
|
|
|
|
|
|
Minimal solution -> (649,180) |
73
|
|
|
|
|
|
|
ok 14 - (649,180) is a working solution 1 |
74
|
|
|
|
|
|
|
ok 15 - (1093435849,303264540) is a working solution 1 |
75
|
|
|
|
|
|
|
ok 16 - (1419278889601,393637139280) is a working solution 1 |
76
|
|
|
|
|
|
|
ok 17 - (1842222905266249,510940703520900) is a working solution 1 |
77
|
|
|
|
|
|
|
ok 18 - (2391203911756701601,663200639532988920) is a working solution 1 |
78
|
|
|
|
|
|
|
ok 19 - (3103780835237293411849,860833919173116097260) is a working solution 1 |
79
|
|
|
|
|
|
|
ok 20 - (4028705132934095091878401,1117361763886065161254560) is a working solution 1 |
80
|
|
|
|
|
|
|
ok 21 - (5229256158767620191964752649,1450334708690193406192321620) is a working solution 1 |
81
|
|
|
|
|
|
|
ok 22 - (6787570465375238075075157060001,1882533334518107155172472208200) is a working solution 1 |
82
|
|
|
|
|
|
|
ok 23 - (8810261234800900253827361899128649,2443526817869794397220462733921980) is a working solution 1 |
83
|
|
|
|
|
|
|
ok 24 - (11435712295201103154229840669911926401,3171695927061658609485005456158521840) is a working solution 1 |
84
|
|
|
|
|
|
|
ok 25 - (14843545748909797093290079362183781339849,4116858869799215005317139861631027426340) is a working solution 1 |
85
|
|
|
|
|
|
|
ok 26 - (19266910946372621425987368782273878267197601,5343679641303454015243038055391617440867480) is a working solution 1 |
86
|
|
|
|
|
|
|
.... |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sqrt[61]= [7,1,4,3,1,2,2,1,3,4,1,14] |
94
|
|
|
|
|
|
|
... |
95
|
|
|
|
|
|
|
Minimal solution -> (1766319049,226153980) |
96
|
|
|
|
|
|
|
ok 14 - (1766319049,226153980) is a working solution 1 |
97
|
|
|
|
|
|
|
ok 15 - (22042834973108102061352541449,2822295814832482312327709940) is a working solution 1 |
98
|
|
|
|
|
|
|
ok 16 - (77869358613928486808166555366140995201,9970149719303180503641083029374964080) is a working solution 1 |
99
|
|
|
|
|
|
|
.... |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head1 CF2fraction($max_iter,$conv_callback,@cont_fraction) |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=over |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=item * @cont_fraction is the continued fraction form of the square root the non-periodic |
107
|
|
|
|
|
|
|
and periodic parts are provided here. |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=item * $conv_callback is a subref which is called whenever a new convergent is found and it's passed as argument |
110
|
|
|
|
|
|
|
to the callback |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=item * $max_iter is the maximum iteration |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=back |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=head1 nth_solution($i) |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
if a minimal solution a+b\sqrt{D} is found , then all solutions can be found by expanding (a+b\sqrt{D})^i = A+B\sqrt{D} |
119
|
|
|
|
|
|
|
so (A,B) is also a solution. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head1 cfrac(D) |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
computes the continued fraction representation of a square root D. it can be proved that the continued fraction will be periodic. |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=head1 find_minimal_sol() |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
returns a minimal solution for the Pell equation by finding the first convergent of \sqrt{D} for which it's numerator and denominator are solutions to the Pell equation. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head1 BIBLIOGRAPHY |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
[1] L. Panaitopol A. Gica - O Introducere in aritmetica si Teoria Numerelor |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head1 SEE ALSO |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
L<http://en.wikipedia.org/wiki/Pell's_equation> |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head1 AUTHOR |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Stefan Petrea, C<< <stefan.petrea at gmail.com> >> |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=cut |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
has minimal_sol => ( |
147
|
|
|
|
|
|
|
isa =>'ArrayRef[Math::BigInt]', |
148
|
|
|
|
|
|
|
is =>'rw', |
149
|
|
|
|
|
|
|
lazy => 1, |
150
|
|
|
|
|
|
|
default => sub {[]}, |
151
|
|
|
|
|
|
|
); |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
has $_ => ( |
154
|
|
|
|
|
|
|
isa => 'Int', |
155
|
|
|
|
|
|
|
is => 'rw', |
156
|
|
|
|
|
|
|
default => 0, |
157
|
|
|
|
|
|
|
) for qw/D debug/; |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub BUILDARGS { |
161
|
|
|
|
|
|
|
my ($self,$param) = @_; |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
confess "D is supposed to be square-free, you passed $param->{D}" |
164
|
|
|
|
|
|
|
if |
165
|
|
|
|
|
|
|
any { $param->{D} % $_ == 0 } |
166
|
|
|
|
|
|
|
map { $_**2 } 2..$param->{D}/2; |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
{ D=> $param->{D} }; |
169
|
|
|
|
|
|
|
}; |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# gets the continued fraction of a square root |
173
|
|
|
|
|
|
|
sub cfrac { |
174
|
|
|
|
|
|
|
my ($self,$D) = @_; |
175
|
|
|
|
|
|
|
my ($n,@m,@d,@a); |
176
|
|
|
|
|
|
|
my $i_rep;#index where a_n starts to repeat |
177
|
|
|
|
|
|
|
my $repeating; #shows if the sequence is repeating or not |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
($repeating,$i_rep,$n,$m[0],$d[0],$a[0] ) = |
180
|
|
|
|
|
|
|
(0 , -1, 0, 0, 1,floor(sqrt($D)) ); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
while(1) { |
183
|
|
|
|
|
|
|
++$n; |
184
|
|
|
|
|
|
|
$m[$n] = $d[$n-1]*$a[$n-1] - $m[$n-1]; |
185
|
|
|
|
|
|
|
$d[$n] = ($D-$m[$n]**2)/$d[$n-1]; |
186
|
|
|
|
|
|
|
$a[$n] = floor(($a[0]+$m[$n])/$d[$n]); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
for (reverse(1..$n-1)) { |
189
|
|
|
|
|
|
|
$repeating= |
190
|
|
|
|
|
|
|
$m[$_] == $m[$n] && |
191
|
|
|
|
|
|
|
$d[$_] == $d[$n] && |
192
|
|
|
|
|
|
|
$a[$_] == $a[$n]; |
193
|
|
|
|
|
|
|
if($repeating) { |
194
|
|
|
|
|
|
|
$i_rep = $_; |
195
|
|
|
|
|
|
|
last; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
}; |
198
|
|
|
|
|
|
|
last if $repeating; |
199
|
|
|
|
|
|
|
}; |
200
|
|
|
|
|
|
|
pop @a; |
201
|
|
|
|
|
|
|
return @a; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# computing a convergent with the continued fraction representation |
206
|
|
|
|
|
|
|
# using http://en.wikipedia.org/wiki/Fundamental_recurrence_formulas |
207
|
|
|
|
|
|
|
# code->(p,q) is run for each of the convergents |
208
|
|
|
|
|
|
|
sub CF2fraction { |
209
|
|
|
|
|
|
|
my ($self,$max_iter,$code,@b) = @_; |
210
|
|
|
|
|
|
|
# print Dumper \@b; |
211
|
|
|
|
|
|
|
my @p = ($b[0],$b[1]*$b[0]+1); |
212
|
|
|
|
|
|
|
my @q = (1 ,$b[1] ); |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
$p[$_] = Math::BigInt->new($p[$_]) for qw/0 1/; |
215
|
|
|
|
|
|
|
$q[$_] = Math::BigInt->new($q[$_]) for qw/0 1/; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
return if |
218
|
|
|
|
|
|
|
$code->($p[0],$q[0]) || |
219
|
|
|
|
|
|
|
$code->($p[1],$q[1]) ; |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
my $n=2; |
222
|
|
|
|
|
|
|
my $jump=0; # jump over the floor part |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
while(1) { |
225
|
|
|
|
|
|
|
print "n= $n p=$p[$n-1] q=$q[$n-1] p/q = ".$p[$n-1]/$q[$n-1]."\n" |
226
|
|
|
|
|
|
|
if $self->debug; |
227
|
|
|
|
|
|
|
if($code) { |
228
|
|
|
|
|
|
|
last if $code->($p[$n-1],$q[$n-1]); # stop if code returns something defined or 1 |
229
|
|
|
|
|
|
|
}; |
230
|
|
|
|
|
|
|
$p[$n] = Math::BigInt->new(0); |
231
|
|
|
|
|
|
|
$q[$n] = Math::BigInt->new(0); |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
my $j = ($n+$jump)%@b; # need to jump over the first number which is the floor of the square root |
234
|
|
|
|
|
|
|
# and keep track of the jump over iterations |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
$j=1,$jump++ if $j==0; # $b[0] is used when $j==0 and we want to avoid that because |
237
|
|
|
|
|
|
|
# that's the first number in the continued fraction representation |
238
|
|
|
|
|
|
|
# and it's not part of the periodic part |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
$p[$n] = $b[$j]*$p[$n-1] + $p[$n-2]; |
241
|
|
|
|
|
|
|
$q[$n] = $b[$j]*$q[$n-1] + $q[$n-2]; |
242
|
|
|
|
|
|
|
last if $n++ > $max_iter; |
243
|
|
|
|
|
|
|
}; |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# returns true if parameters are a solution to the eq and false otherwise |
247
|
|
|
|
|
|
|
sub is_solution { |
248
|
|
|
|
|
|
|
my ($self,$a,$b) = @_; |
249
|
|
|
|
|
|
|
return $a**2 - ($self->D * ($b**2)) == 1; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
sub iterate_convergents { |
255
|
|
|
|
|
|
|
my ($self,$lim,$code) = @_; |
256
|
|
|
|
|
|
|
$self->CF2fraction( |
257
|
|
|
|
|
|
|
$lim, |
258
|
|
|
|
|
|
|
sub { $code->(@_) }, |
259
|
|
|
|
|
|
|
$self->cfrac($self->D) |
260
|
|
|
|
|
|
|
); |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub find_minimal_sol { |
264
|
|
|
|
|
|
|
my ($self) = @_; |
265
|
|
|
|
|
|
|
my @min_sol; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
#search a solution to the Pell equation through the first 100 convergents of sqrt(D) |
268
|
|
|
|
|
|
|
$self->iterate_convergents( |
269
|
|
|
|
|
|
|
100, |
270
|
|
|
|
|
|
|
sub { |
271
|
|
|
|
|
|
|
if( $self->is_solution(@_) ) { # the Pell equation |
272
|
|
|
|
|
|
|
@min_sol = @_; |
273
|
|
|
|
|
|
|
return 1;# stop if minimal solution is found |
274
|
|
|
|
|
|
|
}; |
275
|
|
|
|
|
|
|
0; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
); |
278
|
|
|
|
|
|
|
$self->minimal_sol([@min_sol]); |
279
|
|
|
|
|
|
|
return @min_sol; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub nth_solution { |
284
|
|
|
|
|
|
|
my ($self,$i) = @_; |
285
|
|
|
|
|
|
|
my ($a,$b) = @{$self->minimal_sol}; |
286
|
|
|
|
|
|
|
return ($a,$b) if $i==1; |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
my ($A,$B) = |
289
|
|
|
|
|
|
|
map { Math::BigInt->new($_) } |
290
|
|
|
|
|
|
|
($a,$b); |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
for(2..$i){ |
293
|
|
|
|
|
|
|
my $oldA = $A->copy(); #old values for $A |
294
|
|
|
|
|
|
|
$A = $a*$A + $self->D * $b*$B; |
295
|
|
|
|
|
|
|
$B = $a*$B + $b*$oldA; |
296
|
|
|
|
|
|
|
}; |
297
|
|
|
|
|
|
|
return ($A,$B); |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
1; |