| 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; |