File Coverage

blib/lib/Math/Fraction/Egyptian.pm
Criterion Covered Total %
statement 145 162 89.5
branch 31 42 73.8
condition 13 17 76.4
subroutine 25 25 100.0
pod 15 15 100.0
total 229 261 87.7


line stmt bran cond sub pod time code
1             package Math::Fraction::Egyptian;
2              
3 13     13   12567 use strict;
  13         34  
  13         585  
4 13     13   70 use warnings FATAL => 'all';
  13         23  
  13         684  
5 13     13   76 use base 'Exporter';
  13         23  
  13         1296  
6 13     13   73 use List::Util qw(first reduce max);
  13         26  
  13         30850  
7              
8             our @EXPORT_OK = qw( to_egyptian to_common );
9              
10             our %EXPORT_TAGS = (all => \@EXPORT_OK);
11              
12             our $VERSION = '0.01';
13              
14             my %PRIMES = map { $_ => 1 } primes();
15              
16             =head1 NAME
17              
18             Math::Fraction::Egyptian - construct Egyptian representations of fractions
19              
20             =head1 SYNOPSIS
21              
22             use Math::Fraction::Egyptian ':all';
23             my @e = to_egyptian(43, 48); # returns 43/48 in Egyptian format
24             my @v = to_common(2, 3, 16); # returns 1/2 + 1/3 + 1/16 in common format
25              
26             =head1 DESCRIPTION
27              
28             From L:
29              
30             =over 4
31              
32             An Egyptian fraction is the sum of distinct unit fractions, such as
33              
34             1/2 + 1/3 + 1/16
35              
36             That is, each fraction in the expression has a numerator equal to 1 and a
37             denominator that is a positive integer, and all the denominators differ
38             from each other. The sum of an expression of this type is a positive
39             rational number C; for instance the Egyptian fraction above sums to
40             C<43/48>.
41              
42             Every positive rational number can be represented by an Egyptian fraction.
43             Sums of this type, and similar sums also including C<2/3> and C<3/4> as
44             summands, were used as a serious notation for rational numbers by the
45             ancient Egyptians, and continued to be used by other civilizations into
46             medieval times.
47              
48             In modern mathematical notation, Egyptian fractions have been superseded by
49             L and
50             decimal notation. However, Egyptian fractions continue to be an object of
51             study in modern number theory and recreational mathematics, as well as in
52             modern historical studies of ancient mathematics.
53              
54             =back
55              
56             A common fraction has an infinite number of different Egyptian fraction
57             representations. This module only implements a handful of conversion
58             strategies for conversion of common fractions to Egyptian form; see section
59             L below for details.
60              
61             =head1 FUNCTIONS
62              
63             =head2 to_egyptian($numer, $denom, %attr)
64              
65             Converts fraction C<$numer/$denom> to its Egyptian representation.
66              
67             Example:
68              
69             my @egypt = to_egyptian(5,9); # converts 5/9
70             print "@egypt"; # prints FIXME
71              
72             =cut
73              
74             sub to_egyptian {
75 60     60 1 8016 my ($n,$d,%attr) = @_;
76 60         183 ($n,$d) = (abs(int($n)), abs(int($d)));
77 60   50     475 $attr{dispatch} ||= \&_dispatch;
78              
79             # oh come on
80 60 100       223 if ($d == 0) { die "can't convert $n/$d"; }
  3         29  
81              
82             # handle improper fractions
83 57 100       186 if ($n >= $d) {
84 2         3 my $n2 = $n % $d;
85 2         24 warn "$n/$d is an improper fraction; expanding $n2/$d instead";
86 2         112 $n = $n2;
87             }
88              
89 57         85 my @egypt;
90 57   66     270 while ($n && $n != 0) {
91 53         133 ($n, $d, my @e) = $attr{dispatch}->($n,$d);
92 53         293 push @egypt, @e;
93             }
94 57         673 return @egypt;
95             }
96              
97             # default strategy dispatcher
98             sub _dispatch {
99 53     53   119 my ($n, $d) = @_;
100 53         70 my @egypt;
101              
102 53         410 my @strategies = (
103             [ trivial => \&s_trivial, ],
104             [ small_prime => \&s_small_prime, ],
105             [ practical_strict => \&s_practical_strict, ],
106             [ practical => \&s_practical, ],
107             [ greedy => \&s_greedy, ],
108             );
109              
110             STRATEGY:
111 53         116 for my $s (@strategies) {
112 187         372 my ($name,$coderef) = @$s;
113 187         263 my @result = eval { $coderef->($n,$d); };
  187         548  
114 187 100       812 next STRATEGY if $@;
115 53         156 my ($n2, $d2, @e2) = @result;
116 53         121 ($n,$d) = ($n2,$d2);
117 53         93 push @egypt, @e2;
118 53         166 last STRATEGY;
119             }
120 53         408 return $n, $d, @egypt;
121             }
122              
123             =head2 to_common(@denominators)
124              
125             Converts an Egyptian fraction into a common fraction.
126              
127             Example:
128              
129             my ($num,$den) = to_common(2,5,11); # 1/2 + 1/5 + 1/11 = ?
130             print "$num/$den"; # prints "87/110"
131              
132             =cut
133              
134             sub to_common {
135 8     8 1 5397 my ($n,$d) = (0,1);
136 8         19 for my $a (@_) {
137 18         41 ($n, $d) = simplify($a * $n + $d, $a * $d);
138             }
139 8         34 return ($n,$d);
140             }
141              
142             =head2 GCD($x,$y)
143              
144             Uses Euclid's algorithm to determine the greatest common denominator
145             ("GCD") of C<$x> and C<$y>. Returns the GCD.
146              
147             =cut
148              
149             sub GCD {
150 115     115 1 1424 my ($x, $y) = (int($_[0]), int($_[1]));
151 115 100       299 return ($y) ? GCD($y, $x % $y) : $x;
152             }
153              
154             =head2 simplify($n,$d)
155              
156             Reduces fraction C<$n/$d> to simplest terms.
157              
158             Example:
159              
160             my @x = simplify(25,100); # @x is (1,4)
161              
162             =cut
163              
164             sub simplify {
165 25     25 1 42 my ($n, $d) = @_;
166 25         51 my $gcd = GCD($n,$d);
167 25         95 return ($n / $gcd, $d / $gcd);
168             }
169              
170             =head2 primes()
171              
172             Returns a list of all prime numbers below 1000.
173              
174             =cut
175              
176             sub primes {
177 1161     1161 1 34362 return qw(
178             2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89
179             97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179
180             181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271
181             277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379
182             383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479
183             487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599
184             601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701
185             709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823
186             827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941
187             947 953 967 971 977 983 991 997
188             );
189             }
190              
191             =head2 prime_factors($n)
192              
193             Returns the prime factors of C<$n> as a list of (prime,multiplicity) pairs.
194             The list is sorted by increasing prime number.
195              
196             Example:
197              
198             my @pf = prime_factors(120); # 120 = 2 * 2 * 2 * 3 * 5
199             # @pf = ([2,3],[3,1],[5,1])
200              
201             =cut
202              
203             sub prime_factors {
204 1148     1148 1 2122 my $n = shift;
205 1148         1790 my @primes = primes();
206 1148         8208 my %pf;
207 1148         2275 for my $i (0 .. $#primes) {
208 14483         14417 my $p = $primes[$i];
209 14483         25903 while ($n % $p == 0) {
210 5493         7468 $pf{$p}++;
211 5493         11877 $n /= $p;
212             }
213 14483 100       26292 last if $n == 1;
214             }
215 1148 50       2208 return unless $n == 1;
216 1148         3587 return map { [ $_, $pf{$_} ] } sort { $a <=> $b } keys %pf;
  3628         17541  
  3518         5457  
217             }
218              
219             =head2 decompose($n)
220              
221             If C<$n> is a composite number, returns ($p,$q) such that:
222              
223             * $p != 1
224             * $q != 1
225             * $p x $q == $n
226              
227             =cut
228              
229             sub decompose {
230 5     5 1 20 my @pf = reverse map { ($_->[0]) x $_->[1] } prime_factors($_[0]);
  10         28  
231 5         14 my ($p, $q) = (1, 1);
232 5         9 for my $f (@pf) {
233 11 100       28 if ($p < $q) { $p *= $f }
  6         12  
234 5         11 else { $q *= $f }
235             }
236 5         10 return sort { $a <=> $b } $p, $q;
  5         26  
237             }
238              
239             =head2 sigma(@pairs)
240              
241             Helper function for determining whether a number is "practical" or not.
242              
243             =cut
244              
245             sub sigma {
246             # see http://en.wikipedia.org/wiki/Divisor_function
247 2156     2156 1 6412 my @pairs = @_;
248             my $term = sub {
249 3464     3464   4719 my ($p,$a) = @_;
250 3464         20212 return (($p ** ($a + 1)) - 1) / ($p - 1);
251 2156         7352 };
252 2156     1308   6151 return reduce { $a * $b } map { $term->(@$_) } @pairs;
  1308         8044  
  3464         6385  
253             }
254              
255             =head1 STRATEGIES
256              
257             Fibonacci, in his Liber Abaci, identifies seven different methods for
258             converting common to Egyptian fractions:
259              
260             =over 4
261              
262             =item 1.
263              
264             =item 2.
265              
266             =item 3.
267              
268             =item 4.
269              
270             =item 5.
271              
272             =item 6.
273              
274             =item 7.
275              
276             =back
277              
278             The strategies as implemented below have the following features in common:
279              
280             =over 4
281              
282             =item *
283              
284             Each function call has a signature of the form C($numerator,
285             $denominator)>.
286              
287             =item *
288              
289             The return value from a successful strategy call is the list C<($numerator,
290             $denominator, @egyptian)>: the new numerator, the new denominator, and
291             zero or more new Egyptian factors extracted from the input fraction.
292              
293             =item *
294              
295             Some strategies are not applicable to all inputs. If the strategy
296             determines that it cannot determine the next number in the expansion, it
297             throws an exception (via C) to indicate the strategy is unsuitable.
298              
299             =back
300              
301             =cut
302              
303             =head2 s_trivial($n,$d)
304              
305             Strategy for dealing with "trivial" expansions--if C<$n> is C<1>, then this
306             fraction is already in Egyptian form.
307              
308             Example:
309              
310             my @x = s_trivial(1,5); # @x = (0,1,5)
311              
312             =cut
313              
314             sub s_trivial {
315 55     55 1 1023 my ($n,$d) = @_;
316 55 100 66     249 if (defined($n) && $n == 1) {
317 4         14 return (0,1,$d);
318             }
319 51         396 die "unsuitable strategy";
320             }
321              
322             =head2 s_small_prime($n,$d)
323              
324             For a numerator of 2 with odd prime denominator d, one can use this
325             expansion:
326              
327             2/d = 2/(d + 1) + 2/d(d + 1)
328              
329             =cut
330              
331             sub s_small_prime {
332 52     52 1 1800 my ($n,$d) = @_;
333 52 100 66     497 if ($n == 2 && $d > 2 && $d < 30 && $PRIMES{$d}) {
      100        
      100        
334 9         19 my $x = ($d + 1) / 2;
335 9         44 return (0, 1, $x, $d * $x);
336             }
337             else {
338 43         248 die "unsuitable strategy";
339             }
340             }
341              
342             =head2 s_practical($n,$d)
343              
344             Attempts to find a multiplier C<$M> such that the scaled denominator C<$M *
345             $d> is a practical number. This lets us break up the scaled numerator C<$M
346             * $numer> as in this example:
347              
348             examining 2/9:
349             9 * 2 is 18, and 18 is a practical number
350             choose $M = 2
351              
352             scale 2/9 => 4/18
353             = 3/18 + 1/18
354             = 1/6 + 1/18
355              
356             By definition, all numbers N < P, where P is practical, can be represented
357             as a sum of distinct divisors of P.
358              
359             =cut
360              
361             sub s_practical {
362 43     43 1 1073 my ($n,$d) = @_;
363              
364             # look for a multiple of $d that is a practical number
365 43     541   711 my $M = first { is_practical($_ * $d) } 1 .. $d;
  541         968  
366 43 50       321 die "unsuitable strategy" unless $M;
367              
368 43         118 $n *= $M;
369 43         75 $d *= $M;
370              
371 43         1366 my @divisors = grep { $d % $_ == 0 } 1 .. $d;
  37412         56081  
372              
373 43         1015 my @N;
374             my %seen;
375 43         135 while ($n) {
376 122         204 @divisors = grep { $_ <= $n } @divisors;
  888         1383  
377 122         354 my $x = max @divisors;
378 122         180 push @N, $x;
379 122         175 $n -= $x;
380 122         202 @divisors = grep { $_ < $x } @divisors;
  529         1014  
381             }
382 43         68 my @e = map { $d / $_ } @N;
  122         302  
383 43         263 return (0, 1, @e);
384             }
385              
386             =head2 s_practical_strict($n,$d)
387              
388              
389              
390              
391             =cut
392              
393             sub s_practical_strict {
394 42     42 1 116 my ($N,$D) = @_;
395              
396             # find multiples of $d that are practical numbers
397 42         242 my @mult = grep { is_practical($_ * $D) } 1 .. $D;
  2521         4373  
398              
399 42 50       210 die "unsuitable strategy" unless @mult;
400              
401             MULTIPLE:
402 42         76 for my $M (@mult) {
403 785         1293 my $n = $N * $M;
404 785         927 my $d = $D * $M;
405              
406             # find the divisors of $d
407 785         77614 my @div = grep { $d % $_ == 0 } 1 .. $d;
  2211796         3061151  
408              
409             # expand $n into a sum of divisors of $d
410 785         60411 my @N;
411 785         2456 while ($n) {
412 785 50       4613 next MULTIPLE unless @N;
413 0         0 @div = grep { $_ <= $n } @div;
  0         0  
414 0         0 my $x = max @div;
415 0         0 push @N, $x;
416 0         0 $n -= $x;
417 0         0 @div = grep { $_ < $x } @div;
  0         0  
418             }
419 0         0 my @e = map { $d / $_ } @N;
  0         0  
420              
421 0 0       0 next MULTIPLE if $e[0] != $M;
422 0 0       0 next MULTIPLE if grep { $d % $_ } @e[1 .. $#e]; # FIXME
  0         0  
423              
424             # o
425             # 4. As an observation a1, ..., ai were always divisors of the
426             # denominator a of the first partition 1/a
427              
428 0         0 return (0, 1, @e);
429             }
430 42         768 die "unsuitable strategy";
431             }
432              
433             =head2 is_practical($n)
434              
435             Returns a true value if C<$n> is a practical number.
436              
437             =cut
438              
439             my $_practical;
440             sub is_practical {
441 3090     3090 1 17565 my $n = shift;
442 3090 100       7296 unless (exists $_practical->{$n}) {
443 2199         2992 $_practical->{$n} = _is_practical($n);
444             }
445 3090         7001 return $_practical->{$n};
446             }
447              
448             sub _is_practical {
449 2199     2199   2425 my $n = shift;
450 2199 100       3806 return 1 if $n == 1; # edge case
451 2198 100       5738 return 0 if $n % 2 == 1; # no odd practicals except 1
452 1142         1750 my @pf = prime_factors($n);
453 1142         3539 foreach my $i (1 .. $#pf) {
454 2136         3222 my $p = $pf[$i][0];
455 2136 100       5029 return 0 if ($p > 1 + sigma( @pf[0 .. $i-1]));
456             }
457 682         2883 return 1;
458             }
459              
460             =head2 s_composite($n,$d)
461              
462             From L:
463              
464             =over 4
465              
466             For composite denominators, factored as p×q, one can expand 2/pq using the
467             identity 2/pq = 1/aq + 1/apq, where a = (p+1)/2. Clearly p must be odd.
468              
469             For instance, applying this method for d = pq = 21 gives p=3, q=7, and
470             a=(3+1)/2=2, producing the expansion 2/21 = 1/14 + 1/42.
471              
472             =back
473              
474             =cut
475              
476             sub s_composite {
477 2     2 1 1407 my ($n,$d) = @_;
478 2 100       19 die "unsuitable strategy" if $PRIMES{$d};
479 1         4 my ($p,$q) = decompose($d);
480              
481             # is $p odd
482 1 50       5 if ($p % 2 == 1) {
483 1         2 my $a = ($p + 1) / 2;
484 1         8 return (0, 1, $a * $q, $a * $p * $q);
485             }
486              
487             # is $q odd
488 0 0       0 if ($q % 2 == 1) {
489 0         0 my $a = ($q + 1) / 2;
490 0         0 return (0, 1, $a * $p, $a * $p * $q);
491             }
492              
493 0         0 die "unsuitable strategy";
494             }
495              
496             =head2 s_greedy($n,$d)
497              
498             Implements Fibonacci's greedy algorithm for computing Egyptian fractions:
499              
500             n/d => 1/ceil(d/n) + ((-d)%n)/(d*ceil(d/n))
501              
502             Example:
503              
504             # performing the greedy expansion of 3/7:
505             # ceil(7/3) = 3
506             # new numerator = (-7)%3 = 2
507             # new denominator = 7 * 3 = 21
508             # so 3/7 => 1/3 + 2/21
509              
510             my ($n,$d,$e) = greedy(2,7);
511             print "$n/$d ($e)"; # prints "2/21 (3)"
512              
513             =cut
514              
515             sub s_greedy {
516 13     13   13405 use POSIX 'ceil';
  13         118585  
  13         99  
517 5     5 1 6610 my ($n,$d) = @_;
518 5         39 my $e = ceil( $d / $n );
519 5         24 ($n, $d) = simplify((-1 * $d) % $n, $d * $e);
520 5         18 return ($n, $d, $e);
521             }
522              
523             =head1 AUTHOR
524              
525             John Trammell, C<< gmail com> >>
526              
527             =head1 BUGS
528              
529             Please report any bugs or feature requests to C
530             rt.cpan.org>, or through
531             the web interface at
532             L. I
533             will be notified, and then you'll automatically be notified of progress on your
534             bug as I make changes.
535              
536             =head1 SUPPORT
537              
538             You can find documentation for this module with the perldoc command.
539              
540             perldoc Math::Fraction::Egyptian
541              
542             You can also look for information at:
543              
544             =over 4
545              
546             =item * GitHub
547              
548             L
549              
550             =item * RT: CPAN's request tracker
551              
552             L
553              
554             =item * AnnoCPAN: Annotated CPAN documentation
555              
556             L
557              
558             =item * CPAN Ratings
559              
560             L
561              
562             =item * Search CPAN
563              
564             L
565              
566             =back
567              
568             =head1 RESOURCES
569              
570             =over 4
571              
572             =item L
573              
574             =item L
575              
576             =item L
577              
578             =item L
579              
580             =item L
581              
582             =item L
583              
584             =item L
585              
586             =item L
587              
588             =back
589              
590             =head1 ACKNOWLEDGEMENTS
591              
592             Thanks to Project Euler, L, for stretching my mind
593             into obscure areas of mathematics. C<< :-) >>
594              
595             =head1 COPYRIGHT & LICENSE
596              
597             Copyright 2008 John Trammell, all rights reserved.
598              
599             This program is free software; you can redistribute it and/or modify it
600             under the same terms as Perl itself.
601              
602             =cut
603              
604             1;
605