File Coverage

blib/lib/Math/ContinuedFraction.pm
Criterion Covered Total %
statement 90 140 64.2
branch 18 40 45.0
condition 2 3 66.6
subroutine 13 19 68.4
pod 6 11 54.5
total 129 213 60.5


line stmt bran cond sub pod time code
1             package Math::ContinuedFraction;
2            
3 4     4   261047 use warnings;
  4         12  
  4         149  
4 4     4   23 use strict;
  4         10  
  4         138  
5 4     4   23 use Carp;
  4         12  
  4         362  
6 4     4   4095 use Math::BigInt;
  4         61300  
  4         25  
7 4     4   39222 use Math::BigRat;
  4         79383  
  4         24  
8             #use Smart::Comments;
9            
10             our $VERSION = '0.11';
11            
12             #
13             # $cf = Math::ContinuedFraction->new([1, 1, 1, 1, [3, 2, 3, 2]]);
14             #
15             #
16             sub new
17             {
18 3     3 1 49 my $class = shift;
19 3         8 my $self = {};
20 3         8 my(@seq);
21            
22 3 50       12 if (ref $class)
23             {
24 0 0       0 if ($class->isa(__PACKAGE__))
25             {
26 0         0 $class->_copy($self);
27 0         0 return bless($self, ref $class);
28             }
29            
30 0         0 warn "Attempts to create a Continued Fraction object from a '",
31             ref $class, "' object fail.\n";
32 0         0 return undef;
33             }
34            
35 3         11 bless($self, $class);
36            
37             #
38             # We're not creating a copy of an existing CF, so start from
39             # first principles.
40             #
41 3         20 $self->{simple} = [0];
42 3         11 $self->{repeat} = undef;
43            
44 3 50       13 if (scalar @_)
45             {
46             #
47             # Get the a's and b's.
48             # SHHH! Don't tell anyone about the b's yet, but
49             # they'll get accessed in a later version.
50             #
51 3         7 my($a_ref, $b_ref) = @_;
52            
53 3 50       15 if (ref $a_ref eq "ARRAY")
    0          
54             {
55 3         9 my(@seq) = @$a_ref;
56            
57             #
58             # See if there's a repeating component. If there is, check for
59             # one of those "Why are you doing that" empty array cases.
60             #
61 3 100       18 if (ref $seq[$#seq] eq "ARRAY")
62             {
63 1         2 my @r = @{ pop @seq };
  1         2  
64 1 50       13 $self->{repeat} = [@r] if (scalar @r > 0);
65             }
66            
67             #
68             # Another empty array case check, this one slightly legitimate.
69             #
70 3 50       17 $self->{simple} = (scalar @seq)? [@seq]: [0];
71             }
72             elsif (ref $a_ref eq "Math::BigRat")
73             {
74 0         0 my($n, $d) = $a_ref->parts();
75            
76             #
77             # Do from_ratio stuff.
78             #
79 0         0 $self->from_ratio($n, $d);
80             }
81             else
82             {
83             #
84             # Complain bitterly if we weren't passed an ARRAY or
85             # BigRat reference.
86             #
87 0         0 carp __PACKAGE__ .
88             "->new() takes either an array reference or a Math::BigRat object or another " .
89             __PACKAGE__ . " object";
90 0         0 return undef;
91             }
92             }
93            
94 3         12 return $self;
95             }
96            
97             #
98             # my $cf67_29 = Math::ContinuedFraction->from_ratio(67, 29);
99             #
100             # Create a continued fraction from a simple ratio.
101             # These CFs will always be the simple types.
102             #
103             sub from_ratio
104             {
105 1     1 1 14 my $class = shift;
106 1         1 my($n, $d) = @_;
107 1         4 my $self = {};
108 1         2 my @cf;
109            
110 4     4   6247 use integer;
  4         10  
  4         35  
111            
112             LOOP:
113 1         2 for (;;)
114             {
115 3         6 my $q = $n / $d;
116 3         17 my $r = $n % $d;
117            
118 3         4 push @cf, $q;
119 3 50       8 last LOOP if ($r == 0);
120 3 100       9 if ($r == 1)
121             {
122 1         1 push @cf, $d;
123 1         6 last LOOP;
124             }
125 2         3 $n = $d;
126 2         3 $d = $r;
127             }
128            
129 1         5 $self->{simple} = [@cf];
130 1         3 $self->{repeat} = undef;
131 1         6 return bless($self, $class);
132             }
133            
134             #
135             # $qs = Math::ContinuedFraction->from_quadratic($a, $b, $c);
136             #
137             sub from_root
138             {
139 0     0 0 0 my $class = shift;
140 0         0 my($dis) = @_;
141 0         0 my $self = {};
142 0         0 my(@repeat);
143            
144 0         0 my($p, $q) = (0, 1);
145 0         0 my($a0, $a, $last);
146 0         0 $last = 2 * ($a0 = $a = int(sqrt($dis)));
147            
148 0         0 for (;;)
149             {
150 0         0 $p = $a * $q - $p;
151 0         0 $q = ($dis - $p**2)/$q;
152 0         0 $a = int(($a0 + $p)/$q);
153 0         0 push @repeat, $a;
154 0 0       0 last if ($last == $a);
155             }
156            
157 0         0 $self->{simple} = [$a0];
158 0         0 $self->{repeat} = [@repeat];
159 0         0 return bless($self, $class);
160             }
161            
162             #
163             # $qs = Math::ContinuedFraction->from_quadratic($a, $b, $c);
164             #
165             sub from_quadratic
166             {
167 0     0 0 0 my $self = shift;
168 0         0 my(@coefficients) = @_;
169            
170 0         0 while (@coefficients)
171             {
172             }
173             }
174            
175             #
176             # if ($cf->is_finite()) { ...
177             #
178             #
179             #
180             sub is_finite
181             {
182 0     0 0 0 my $self = shift;
183 0 0       0 return ($self->{repeat})? 1: 0;
184             }
185            
186             #
187             # my($slength, $rlength) = $cf->sequence_length();
188             #
189             #
190             sub sequence_length
191             {
192 9     9 0 14 my $self = shift;
193 9         12 my $sl = scalar @{ $self->{simple} };
  9         22  
194 9 100       27 my $rl = ($self->{repeat})? scalar @{ $self->{repeat} }: 0;
  4         8  
195            
196 9         22 return ($sl, $rl);
197             }
198            
199             #
200             # $bigratio = $cf->brconvergent($nth);
201             #
202             # Exactly like the convergent() method, except returning a BigRat
203             # type instead of separate BigInt numerator and denominator.
204             #
205             sub brconvergent
206             {
207 0     0 1 0 my $self = shift;
208 0         0 my($terms) = @_;
209            
210 0         0 my($n, $d) = $self->convergent($terms);
211 0         0 return Math::BigRat->new($n, $d);
212             }
213            
214             #
215             # ($numerator, $denominator) = $cf->convergent($nth);
216             #
217             # Get the fraction for the continued fraction at the nth term.
218             #
219             sub convergent
220             {
221 9     9 1 5408 my $self = shift;
222 9         17 my($terms) = @_;
223 9         17 my($repetitions, $remainder) = (0, 0);
224 9         24 my($sl, $rl) = $self->sequence_length();
225            
226 4     4   2227 use integer;
  4         10  
  4         17  
227            
228             #
229             ### $terms
230             ### $sl
231             ### $rl
232             #
233 9         53 my $n = Math::BigInt->new(0);
234 9         958 my $d = Math::BigInt->new(1);
235            
236 9 50       455 $terms = $sl + $rl unless ($terms);
237 9 50 66     47 $terms = $sl if ($terms > $sl and $rl == 0);
238            
239 9 100       27 if ($terms >= $sl)
240             {
241 4         9 $repetitions = ($terms - $sl) / $rl;
242 4         19 $remainder = ($terms - $sl) % $rl;
243            
244             #
245             ### $repetitions
246             ### $remainder
247             #
248 4 50       10 if ($remainder > 0)
249             {
250 0         0 my @remaining = (@{ $self->{repeat} }[0..$remainder]);
  0         0  
251 0         0 ($n, $d) = $self->evaluate(\@remaining, $n, $d);
252             }
253            
254 4         11 for (1..$repetitions)
255             {
256 10         30 ($n, $d) = $self->evaluate($self->{repeat}, $n, $d);
257             }
258            
259 4         13 return reverse $self->evaluate($self->{simple}, $n, $d);
260             }
261            
262 5         10 my @partial = @{ $self->{simple} }[0..$terms];
  5         16  
263 5         17 return reverse $self->evaluate(\@partial, $n, $d);
264             }
265            
266             sub evaluate
267             {
268 19     19 0 24 my $self = shift;
269 19         98 my($sequence, $n, $d) = @_;
270            
271             #
272             ### $sequence
273             ### $n
274             ### $d
275             #
276             # Add on the next group of continued fraction terms.
277             #
278             # a0 + 1
279             # ------
280             # a1 + 1
281             # ------
282             # a2 + n
283             # ---
284             # d
285             #
286 19         53 foreach my $a_k (reverse @$sequence)
287             {
288 34         105 $n += $d * $a_k;
289 34         7099 ($n, $d) = ($d, $n); # Reciprocal
290             }
291            
292 19         98 return ($n, $d);
293             }
294            
295             #
296             # Get the array form of the continued fraction.
297             #
298             sub to_array
299             {
300 0     0 1 0 my $self = shift;
301 0         0 my $v = $self->{simple};
302 0 0       0 push @{ $v }, $self->{repeat} if ($self->{repeat});
  0         0  
303            
304 0         0 return $v;
305             }
306            
307             sub to_ascii
308             {
309 2     2 1 15 my $self = shift;
310 2         5 my $cf = '[' . join(", ", @{ $self->{simple} });
  2         18  
311 2 50       12 $cf .= ', [' . join(", ", @{ $self->{repeat} }) . ']' if ($self->{repeat});
  0         0  
312 2         9 return $cf .']';
313             }
314            
315             #
316             # $class->_copy($self);
317             #
318             # Duplicate the continued fraction object.
319             #
320             sub _copy
321             {
322 0     0     my($other, $self) = @_;
323            
324             #
325             # Direct copy of all keys, except for our arrays, which
326             # we'll do with a deeper copy.
327             #
328 0           foreach my $k (grep($_ !~ /simple|repeat/, keys %{$other}))
  0            
329             {
330 0           $self->{$k} = $other->{$k};
331             }
332            
333 0           $self->{simple} = [ @$other->{simple} ];
334 0 0         $self->{repeat} = ($other->{repeat})? [ @$other->{repeat} ]: undef;
335            
336 0           return $self;
337             }
338            
339            
340             =head1 NAME
341            
342             Math::ContinuedFraction - Create and Manipulate Continued Fractions.
343            
344             =head1 SYNOPSIS
345            
346             Quick summary of what the module does.
347            
348             Perhaps a little code snippet.
349            
350             use Math::ContinuedFraction;
351            
352             #
353             # Create new continued fraction objects.
354             #
355             my $cf = Math::ContinuedFraction->new([1, 4, 9, 25]);
356             my $cf_phi = Math::ContinuedFraction->new([1, [1]]);
357            
358             my $cf_67div29 = Math::ContinuedFraction->from_ratio(67, 29);
359            
360            
361             =head1 DESCRIPTION
362            
363             Continued fractions are expressions of the form
364            
365             b1
366             a1 + -------
367             b2
368             a2 + -------
369             b3
370             a3 + -------
371             ...
372            
373             For most instances, the 'b' terms are 1, and the continued fraction
374             can be written as C<[a1, a2, a3, ...]>, etc. If the sequence of 'a' terms ends
375             at a certain point, the continued fraction is known as a finite continued
376             fraction, and can be exactly represented as a fraction. If the sequence of
377             'a' terms has a repeating sequence, it is normally written as
378            
379             ______
380             [a1, a2, a3, a4, a5]
381            
382             where the line over a4 and a5 indicates that they repeat forever. Since we
383             can't use that method in perl code, we indicate the repeating portion by using an
384             array within the array:
385            
386             [a1, a2, a3, [a4, a5]]
387            
388             Note that in the examples in the L, C<$cf_phi> is created using
389             that notation.
390            
391             =head2 Methods to Create Continued Fraction Objects
392            
393             =head3 new()
394            
395             Create a new continued fraction object from an array.
396            
397             my $cf = Math::ContinuedFraction([1, [2, 1]]);
398            
399             Arrays are in the form C<[finite_sequence, [repeating_sequence]]>. A continued fraction
400             with no repeating part simply omits the embedded array reference:
401            
402             my $cf = Math::ContinuedFraction([1, 2, 1, 3, 1, 5]);
403            
404             =head3 from_ratio()
405            
406             Generate a continued fraction from a pair of relatively prime numbers.
407            
408             =head2 Methods to Return Information
409            
410             =head3 convergent()
411            
412             Returns the fraction formed by calculating the rational approximation
413             of the continued fraction at a stopping point, and returning the
414             numerator and denominator.
415            
416             Convergent term counts begin at 1. Continued fractions with a repeating
417             component can effectively have a term count as high as you like. Finite
418             continued fractions will stop at the end of the sequence without warning.
419            
420             #
421             # Find the ratios that approximate pi.
422             #
423             # The array stops at seven elements for simplicity's sake,
424             # the sequence actually does not end.
425             #
426             my $cfpi = Math::ContinuedFraction([3, 7, 15, 1, 292, 1, 1]);
427            
428             for my $j (1..4)
429             {
430             my($n, $d) = cfpi->convergent($j);
431             print $n->bstr() . "/". $d->bstr() . "\n";
432             }
433            
434             The values returned are objects of type Math::BigInt.
435            
436             =head3 brconvergent()
437            
438             Behaves identically to convergent(), but returns a single Math::BigRat
439             object instead of two Math::BigInt objects.
440            
441             #
442             # Find the ratios that approximate pi.
443             #
444             # The array stops at seven elements for simplicity's sake,
445             # the sequence actually does not end.
446             #
447             my $cfpi = Math::ContinuedFraction([3, 7, 15, 1, 292, 1, 1]);
448            
449             for my $j (1..4)
450             {
451             my $r = cfpi->convergent($j);
452             print $r->bstr() . "\n";
453             }
454            
455            
456             =head3 to_array()
457            
458             Returns an array reference that can be used to create a continued fraction (see L).
459            
460             my $cf = Math::ContinuedFraction->from_ratio(0xfff1, 0x7fed);
461             my $aref = $cf->to_array()
462             my $cf2 = Math::ContinuedFraction->new($aref);
463            
464             =head3 to_ascii()
465            
466             Returns the string form of the array reference.
467            
468             my $cf = Math::ContinuedFraction->from_ratio(0xfff1, 0x7fed);
469             print $cf->to_ascii(), "\n";
470            
471             Returns C<[2, 1432, 1, 6, 1, 2]>.
472            
473             =head1 AUTHOR
474            
475             John Gamble, C<< >>
476            
477             =head1 ACKNOWLEDGEMENTS
478            
479             Olds, C. D. I. New York: Random House, 1963.
480            
481             =head1 COPYRIGHT & LICENSE
482            
483             Copyright 2011 John Gamble.
484            
485             This program is free software; you can redistribute it and/or modify it
486             under the terms of either: the GNU General Public License as published
487             by the Free Software Foundation; or the Artistic License.
488            
489             See http://dev.perl.org/licenses/ for more information.
490            
491            
492             =cut
493            
494             1; # End of Math::ContinuedFraction