File Coverage

blib/lib/Math/ContinuedFraction.pm
Criterion Covered Total %
statement 112 157 71.3
branch 22 54 40.7
condition 3 18 16.6
subroutine 15 28 53.5
pod 6 16 37.5
total 158 273 57.8


line stmt bran cond sub pod time code
1             package Math::ContinuedFraction;
2              
3 5     5   471397 use 5.010001;
  5         54  
4              
5 5     5   31 use warnings;
  5         10  
  5         137  
6 5     5   25 use strict;
  5         10  
  5         107  
7 5     5   24 use Carp;
  5         19  
  5         285  
8 5     5   3547 use Math::BigInt;
  5         86351  
  5         49  
9 5     5   82542 use Math::BigRat;
  5         132693  
  5         27  
10             #use Smart::Comments;
11              
12             use overload
13 0     0   0 '+' => sub {return Continued::Fraction->add($_[0], $_[1]);},
14 0     0   0 '-' => sub {return Continued::Fraction->subt($_[0], $_[1]);},
15 0     0   0 '*' => sub {return Continued::Fraction->mult($_[0], $_[1]);},
16 0     0   0 '/' => sub {return Continued::Fraction->div($_[0], $_[1]);},
17 5     5   5718 ;
  5         13  
  5         70  
18              
19             our $VERSION = '0.14';
20              
21             =pod
22              
23             =encoding UTF-8
24              
25             =head1 NAME
26              
27             Math::ContinuedFraction - Create and Manipulate Continued Fractions.
28              
29             =head1 SYNOPSIS
30              
31             Quick summary of what the module does.
32              
33             Perhaps a little code snippet.
34              
35             use Math::ContinuedFraction;
36              
37             #
38             # Create new continued fraction objects.
39             #
40             my $cf = Math::ContinuedFraction->new([1, 4, 9, 25]);
41             my $cf_phi = Math::ContinuedFraction->new([1, [1]]);
42            
43             my $cf_67div29 = Math::ContinuedFraction->from_ratio(67, 29);
44              
45              
46             =head1 DESCRIPTION
47              
48             Continued fractions are expressions of the form
49              
50             b1
51             a1 + -------
52             b2
53             a2 + -------
54             b3
55             a3 + -------
56             ...
57              
58             For most instances, the 'b' terms are 1, and the continued fraction
59             can be written as C<[a1, a2, a3, ...]>, etc. If the sequence of 'a' terms ends
60             at a certain point, the continued fraction is known as a finite continued
61             fraction, and can be exactly represented as a fraction. If the sequence of
62             'a' terms has a repeating sequence, it is normally written as
63              
64             ______
65             [a1, a2, a3, a4, a5]
66              
67             where the line over a4 and a5 indicates that they repeat forever. Since we
68             can't use that method in perl code, we indicate the repeating portion by
69             using an array within the array:
70              
71             [a1, a2, a3, [a4, a5]]
72              
73             Note that in the examples in the L, C<$cf_phi> is created using
74             that notation.
75              
76             =head2 Methods to Create Continued Fraction Objects
77              
78             =head3 new()
79              
80             Create a new continued fraction object from an array or the
81             ratio of two numbers.
82              
83             my $cf = Math::ContinuedFraction([1, [2, 1]]);
84              
85             Arrays are in the form C<[finite_sequence, [repeating_sequence]]>. A
86             continued fraction with no repeating part simply omits the embedded
87             array reference:
88              
89             $cf = Math::ContinuedFraction([1, 2, 1, 3, 1, 5]);
90             $cf = Math::ContinuedFraction->new([1, 71, 13, 8]);
91             $cf = Math::ContinuedFraction->new([1, 2, 1, 2, [3, 2, 3, 2]]);
92              
93             A continued fraction may be created from a ratio between two numbers.
94             Be sure not to put the numbers in an array, as
95              
96             #
97             # Find the CF form of 121/23.
98             #
99             $cf = Math::ContinuedFraction->new(121, 23);
100              
101             is different from
102              
103             #
104             # Find the CF of
105             # 121 + 1
106             # -----
107             # 23
108             #
109             $cf = Math::ContinuedFraction->new([121, 23]);
110              
111              
112             The ratio may consist of Math::BigInt objects.
113              
114             $big_n = Math::BigInt->new("0xccc43c90d2c0");
115             $big_q = Math::BigInt->new("0xb2069d579ddb");
116             $cf = Math::ContinuedFraction->new($big_n, $big_q);
117              
118             A Math::BigRat object will also work:
119              
120             $bratio = Math::BigRat->new("0xccc43c90d2c0", "0xb2069d579ddb");
121             $cf = Math::ContinuedFraction->new($bratio);
122              
123             =cut
124              
125             sub new
126             {
127 3     3 1 814 my $class = shift;
128 3         7 my $self = {};
129              
130 3 50       16 if (ref $class)
131             {
132 0 0       0 if ($class->isa(__PACKAGE__))
133             {
134 0         0 $class->_copy($self);
135 0         0 return bless($self, ref $class);
136             }
137              
138 0         0 warn "Attempts to create a Continued Fraction object from a '",
139             ref $class, "' object fail.\n";
140 0         0 return undef;
141             }
142              
143 3         8 bless($self, $class);
144              
145             #
146             # We're not creating a copy of an existing CF, so start from
147             # first principles.
148             #
149 3         79 $self->{simple_a} = [0];
150 3         8 $self->{repeat_a} = undef;
151 3         8 $self->{simple_b} = undef;
152 3         9 $self->{repeat_b} = undef;
153              
154 3 50       12 if (scalar @_)
155             {
156             #
157             # Get the a's and b's.
158             #
159 3         10 my($a_ref, $b_ref) = @_;
160              
161 3 50 0     32 if (ref $a_ref eq "ARRAY")
    0 0        
    0 0        
    0 0        
    0          
162             {
163 3         12 my(@seq) = @$a_ref;
164              
165             #
166             # See if there's a repeating component. If there is,
167             # check for one of those "Why are you doing that"
168             # empty array cases.
169             #
170 3 100       31 if (ref $seq[$#seq] eq "ARRAY")
171             {
172 1         2 my @r = @{ pop @seq };
  1         3  
173 1 50       6 $self->{repeat_a} = [@r] if (scalar @r > 0);
174             }
175              
176             #
177             # Another empty array case check, this one slightly
178             # legitimate.
179             #
180 3 50       19 $self->{simple_a} = (scalar @seq)? [@seq]: [0];
181              
182             #
183             # Now check for a second ARRAY component, which
184             # will act as a numerator in the written-out
185             # version of the continued fraction.
186             #
187 3 50 33     16 if (defined $b_ref and ref $b_ref eq "ARRAY")
188             {
189 0         0 my(@seq) = @$b_ref;
190              
191 0 0       0 if (ref $seq[$#seq] eq "ARRAY")
192             {
193 0         0 my @r = @{ pop @seq };
  0         0  
194 0 0       0 $self->{repeat_b} = [@r] if (scalar @r > 0);
195             }
196 0 0       0 $self->{simple_b} = (scalar @seq)? [@seq]: [0];
197             }
198             }
199             elsif (ref $a_ref eq "Math::BigRat")
200             {
201 0         0 my($n, $d) = $a_ref->parts();
202              
203             #
204             # Do from_ratio stuff.
205             #
206 0         0 $self->from_ratio($n, $d);
207             }
208             elsif (ref $a_ref eq "Math::BigInt" and
209             ref $b_ref eq "Math::BigInt")
210             {
211             #
212             # Do from_ratio stuff.
213             #
214 0         0 $self->from_ratio($a_ref, $b_ref);
215             }
216             elsif (ref $a_ref eq "Math::NumSeq")
217             {
218             }
219             elsif (ref $a_ref eq '' and ref $b_ref eq '' and
220             defined($a_ref) and defined($b_ref))
221             {
222             #
223             # Do from_ratio stuff.
224             #
225 0         0 $self->from_ratio($a_ref, $b_ref);
226             }
227             else
228             {
229             #
230             # Complain bitterly if we weren't passed an ARRAY,
231             # BigRat or BigInt references, or just a pair of
232             # numbers.
233             #
234 0         0 carp "Error." . __PACKAGE__ .
235             "->new() takes either an array reference, " .
236             "or a Math::BigRat object, " .
237             "or a pair of Math::BigInt objects, " .
238             "or another " . __PACKAGE__ . " object";
239 0         0 return undef;
240             }
241             }
242              
243 3         10 return $self;
244             }
245              
246             =head3 from_ratio()
247              
248             Generate a continued fraction from a pair of relatively prime numbers.
249              
250              
251             my $cf67_29 = Math::ContinuedFraction->from_ratio(67, 29);
252              
253             Create a continued fraction from a simple ratio.
254             These CFs will always be the simple types.
255              
256             =cut
257              
258             sub from_ratio
259             {
260 1     1 1 113 my $class = shift;
261 1         4 my($n, $d) = @_;
262 1         3 my $self = {};
263 1         2 my @cf;
264              
265             LOOP:
266 1         2 for (;;)
267             {
268 3         10 my $q = int($n/$d);
269 3         7 my $r = $n % $d;
270              
271 3         5 push @cf, $q;
272 3 50       7 last LOOP if ($r == 0);
273 3 100       8 if ($r == 1)
274             {
275 1         17 push @cf, $d;
276 1         5 last LOOP;
277             }
278 2         11 $n = $d;
279 2         5 $d = $r;
280             }
281              
282 1         5 $self->{simple_a} = [@cf];
283 1         3 $self->{repeat_a} = undef;
284 1         4 return bless($self, $class);
285             }
286              
287             #
288             # $qs = Math::ContinuedFraction->from_root($x);
289             #
290             sub from_root
291             {
292 2     2 0 644 my $class = shift;
293 2         7 my($dis) = @_;
294 2         4 my $self = {};
295              
296 2         6 my($p, $q) = (0, 1);
297 2         4 my($a0, $a, $last);
298 2         10 $last = 2 * ($a0 = $a = int(sqrt($dis)));
299              
300 2         3 my @repeat;
301              
302 2         4 for (;;)
303             {
304 9         15 $p = $a * $q - $p;
305 9         16 $q = ($dis - $p**2)/$q;
306 9         28 $a = int(($a0 + $p)/$q);
307 9         14 push @repeat, $a;
308 9 100       26 last if ($last == $a);
309             }
310              
311 2         6 $self->{simple_a} = [$a0];
312 2         6 $self->{repeat_a} = [@repeat];
313 2         13 return bless($self, $class);
314             }
315              
316             #
317             # $qs = Math::ContinuedFraction->from_quadratic($a, $b, $c);
318             #
319             sub from_quadratic
320             {
321 0     0 0 0 my $self = shift;
322 0         0 my(@coefficients) = @_;
323              
324 0         0 while (@coefficients)
325             {
326             }
327             }
328              
329             #
330             # "... every periodic simple continued fraction CF represents a
331             # quadratic irrational (c + f*sqrt(d))/b, where b,c,f,d are integers
332             # and d is squarefree."
333             # OEIS, A246904
334             #
335             sub to_qirrational
336       0 0   {
337             }
338              
339             #
340             # if ($cf->is_finite()) { ...
341             #
342             #
343             #
344             sub is_finite
345             {
346 0     0 0 0 my $self = shift;
347 0 0       0 return ($self->{repeat_a})? 1: 0;
348             }
349              
350             #
351             # my($slength, $rlength) = $cf->sequence_length();
352             #
353             #
354             sub sequence_length
355             {
356 23     23 0 36 my $self = shift;
357 23         32 my $sl = scalar @{ $self->{simple_a} };
  23         49  
358 23 100       57 my $rl = ($self->{repeat_a})? scalar @{ $self->{repeat_a} }: 0;
  11         18  
359              
360 23         51 return ($sl, $rl);
361             }
362              
363             #
364             # Some OEIS sequences.
365             #
366             # e: A0031417
367             # pi: A001203
368             #
369             my $oeis_e = [
370             2, 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8, 1, 1, 10, 1,
371             1, 12, 1, 1, 14, 1, 1, 16, 1, 1, 18, 1, 1, 20, 1, 1,
372             22, 1, 1, 24, 1, 1, 26, 1, 1, 28, 1, 1, 30, 1, 1, 32,
373             1, 1, 34, 1, 1, 36, 1, 1, 38, 1, 1, 40, 1, 1, 42, 1,
374             1, 44, 1, 1, 46, 1, 1, 48, 1, 1, 50, 1, 1, 52, 1, 1,
375             54, 1, 1, 56, 1, 1, 58, 1, 1, 60, 1, 1, 62, 1, 1, 64,
376             1, 1, 66];
377              
378             my $oeis_pi = [
379             3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 2, 1, 1,
380             2, 2, 2, 2, 1, 84, 2, 1, 1, 15, 3, 13, 1, 4, 2, 6,
381             6, 99, 1, 2, 2, 6, 3, 5, 1, 1, 6, 8, 1, 7, 1, 2,
382             3, 7, 1, 2, 1, 1, 12, 1, 1, 1, 3, 1, 1, 8, 1, 1,
383             2, 1, 6, 1, 1, 5, 2, 2, 3, 1, 2, 4, 4, 16, 1, 161,
384             45, 1, 22, 1, 2, 2, 1, 4, 1, 2, 24, 1, 2, 1, 3, 1,
385             2, 1];
386              
387             =head3 brconvergent()
388              
389             Behaves identically to convergent(), but returns a single Math::BigRat
390             object instead of two Math::BigInt objects.
391              
392             #
393             # Find the ratios that approximate pi.
394             #
395             # The array stops at seven elements for simplicity's sake,
396             # the sequence actually does not end. See sequence A001203
397             # at the Online Encyclopedia of Integer Sequences (http://www.oeis.org/)
398             #
399             my $cfpi = Math::ContinuedFraction([3, 7, 15, 1, 292, 1, 1]);
400              
401             for my $j (1..4)
402             {
403             my $r = cfpi->brconvergent($j);
404             print $r->bstr() . "\n";
405             }
406              
407             =cut
408              
409             sub brconvergent
410             {
411 6     6 1 9717 my $self = shift;
412 6         14 my($terms) = @_;
413              
414 6         16 my($n, $d) = $self->convergent($terms);
415 6         31 return Math::BigRat->new($n, $d);
416             }
417              
418             =head2 Methods to Return Information
419              
420             =head3 convergent()
421              
422             Returns the fraction formed by calculating the rational approximation
423             of the continued fraction at a stopping point, and returning the
424             numerator and denominator.
425              
426             Convergent term counts begin at 1. Continued fractions with a repeating
427             component can effectively have a term count as high as you like. Finite
428             continued fractions will stop at the end of the sequence without warning.
429              
430             #
431             # Find the ratios that approximate pi.
432             #
433             # The array stops at seven elements for simplicity's sake,
434             # the sequence actually does not end.
435             #
436             my $cfpi = Math::ContinuedFraction([3, 7, 15, 1, 292, 1, 1]);
437              
438             for my $j (1..4)
439             {
440             my($n, $d) = cfpi->convergent($j);
441             print $n->bstr() . "/". $d->bstr() . "\n";
442             }
443              
444             The values returned are objects of type Math::BigInt.
445              
446             ($numerator, $denominator) = $cf->convergent($nth);
447              
448             Get the fraction for the continued fraction at the nth term.
449              
450             =cut
451              
452             sub convergent
453             {
454 23     23 1 8106 my $self = shift;
455 23         45 my($terms) = @_;
456 23         52 my($repetitions, $remainder) = (0, 0);
457 23         48 my($sl, $rl) = $self->sequence_length();
458              
459             #
460             ### $terms
461             ### $sl
462             ### $rl
463             #
464 23         84 my $n = Math::BigInt->new(0);
465 23         2592 my $d = Math::BigInt->new(1);
466              
467 23 50       860 $terms = $sl + $rl unless ($terms);
468 23 50 66     87 $terms = $sl if ($terms > $sl and $rl == 0);
469              
470 23 100       55 if ($terms > $sl)
471             {
472 10         26 $repetitions = int(($terms - $sl) / $rl);
473 10         18 $remainder = ($terms - $sl) % $rl;
474              
475             #
476             ### $repetitions
477             ### $remainder
478             #
479 10 50       21 if ($remainder > 0)
480             {
481 0         0 my @remaining = (@{ $self->{repeat_a} }[0..$remainder]);
  0         0  
482 0         0 ($n, $d) = $self->evaluate(\@remaining, $n, $d);
483             }
484              
485 10         22 for (1..$repetitions)
486             {
487 55         131 ($n, $d) = $self->evaluate($self->{repeat_a}, $n, $d);
488             }
489              
490 10         22 return reverse $self->evaluate($self->{simple_a}, $n, $d);
491             }
492              
493 13         38 my @partial = @{ $self->{simple_a} }[0..$terms];
  13         46  
494 13         36 return reverse $self->evaluate(\@partial, $n, $d);
495             }
496              
497             sub evaluate
498             {
499 78     78 0 118 my $self = shift;
500 78         138 my($sequence, $n, $d) = @_;
501              
502             #
503             ### $sequence
504             ### $n
505             ### $d
506             #
507             # Add on the next group of continued fraction terms.
508             #
509             # a0 + 1
510             # ------
511             # a1 + 1
512             # ------
513             # a2 + n
514             # ---
515             # d
516             #
517 78         157 foreach my $a_k (reverse @$sequence)
518             {
519 121         302 $n += $d * $a_k;
520 121         26984 ($n, $d) = ($d, $n); # Reciprocal
521             }
522              
523 78         271 return ($n, $d);
524             }
525              
526             =head3 to_array()
527              
528             Returns an array reference that can be used to create a continued
529             fraction (see L).
530              
531             my $cf = Math::ContinuedFraction->from_ratio(0xfff1, 0x7fed);
532             my $aref = $cf->to_array()
533             my $cf2 = Math::ContinuedFraction->new($aref);
534              
535             =cut
536              
537             sub to_array
538             {
539 0     0 1 0 my $self = shift;
540 0         0 my $v = $self->{simple_a};
541 0 0       0 push @{ $v }, $self->{repeat_a} if ($self->{repeat_a});
  0         0  
542              
543 0         0 return $v;
544             }
545              
546             =head3 to_ascii()
547              
548             Returns the string form of the array reference.
549              
550             my $cf = Math::ContinuedFraction->from_ratio(0xfff1, 0x7fed);
551             print $cf->to_ascii(), "\n";
552              
553             Returns C<[2, 1432, 1, 6, 1, 2]>.
554              
555             =cut
556              
557             sub to_ascii
558             {
559 4     4 1 22 my $self = shift;
560 4         8 my $cf = '[' . join(", ", @{ $self->{simple_a} });
  4         68  
561 4 100       15 $cf .= ', [' . join(", ", @{ $self->{repeat_a} }) . ']' if ($self->{repeat_a});
  2         10  
562 4         14 return $cf .']';
563             }
564              
565             #
566             #
567             #
568             sub add
569             {
570 0     0 0   my $self = shift;
571             }
572              
573             sub subt
574             {
575 0     0 0   my $self = shift;
576             }
577              
578             sub mult
579             {
580 0     0 0   my $self = shift;
581             }
582              
583             sub div
584             {
585 0     0 0   my $self = shift;
586             }
587              
588              
589             #
590             # $class->_copy($self);
591             #
592             # Duplicate the continued fraction object.
593             #
594             sub _copy
595             {
596 0     0     my($other, $self) = @_;
597              
598             #
599             # Direct copy of all keys, except for our arrays, which
600             # we'll do with a deeper copy.
601             #
602 0           foreach my $k (grep($_ !~ /simple|repeat/, keys %{$other}))
  0            
603             {
604 0           $self->{$k} = $other->{$k};
605             }
606              
607 0           $self->{simple_a} = [ @$other->{simple_a} ];
608 0 0         $self->{repeat_a} = ($other->{repeat_a})? [ @$other->{repeat_a} ]: undef;
609              
610 0           return $self;
611             }
612              
613             1;
614             __END__