File Coverage

blib/lib/Math/Fraction.pm
Criterion Covered Total %
statement 410 537 76.3
branch 173 264 65.5
condition 59 101 58.4
subroutine 40 51 78.4
pod 22 33 66.6
total 704 986 71.4


line stmt bran cond sub pod time code
1             package Math::Fraction;
2              
3             # Purpose: To Manipulate Exact Fractions
4             #
5             # Copyright 1997 by Kevin Atkinson (kevina@cark.net)
6             # Version .53b (2 Feb 1998)
7             # Beta Release
8             # Originally Developed with Perl v 5.003_37 for Win32.
9             # Has been testing on Perl Ver 5.003 on a solaris machine and Perl 5.004
10             # on Windows 95
11             # Built on a Linux 2 machine with perl v5.003
12             #
13             # Please send me feedback at kevina@clark.net
14              
15 1     1   5 use vars qw($VERSION @ISA @EXPORT);
  1         1  
  1         120  
16              
17             require Exporter;
18             $VERSION = "0.53";
19             @ISA = qw(Exporter);
20             @EXPORT = qw(frac);
21             @EXPORT_OK = qw(reduce string decimal num list is_tag);
22             %EXPORT_TAGS = (
23             STR_NUM => [qw(string decimal num)],
24             );
25              
26 1     1   4 use Carp;
  1         1  
  1         67  
27 1     1   5 use strict;
  1         1  
  1         36  
28 1     1   3544 use Math::BigInt;
  1         26155  
  1         6  
29 1     1   20108 use Math::BigFloat;
  1         21751  
  1         7  
30             use overload
31 1         6 "+" => "add",
32             "-" => "sub",
33             "*" => "mul",
34             "/" => "div",
35             "abs" => "abs",
36             "**" => "pow",
37             "sqrt"=> "sqrt",
38             "<=>" => "cmp",
39             '""' => "string",
40             "0+" => "decimal",
41 1     1   835 "fallback" => 1;
  1         3  
42              
43             my %DEF = (
44             CURRENT => {TAGS => ['NORMAL','REDUCE','SMALL','AUTO'], DIGITS => undef, SYSTEM => 1, NAME => 'DEFAULT'},
45             DEFAULT => {TAGS => ['NORMAL','REDUCE','SMALL','AUTO'], DIGITS => undef, READONLY=>1, SYSTEM=>1},
46             BLANK => {TAGS => ['','',''] , DIGITS => '' , READONLY=>1, SYSTEM=>1},
47             );
48              
49             my ($OUTFORMAT, $REDUCE, $SIZE, $AUTO, $INTERNAL, $RED_STATE) = (0..5);
50             my $TAG_END = 3; #Last index of tags ment to be kept.
51              
52             my %TAGS = (
53             NORMAL => [$OUTFORMAT, 'NORMAL'],
54             MIXED => [$OUTFORMAT, 'MIXED'],
55             MIXED_RAW => [$OUTFORMAT, 'MIXED_RAW'],
56             RAW => [$OUTFORMAT, 'RAW'],
57             DEF_MIXED => [$OUTFORMAT, undef],
58             REDUCE => [$REDUCE, 'REDUCE'],
59             NO_REDUCE => [$REDUCE, 'NO_REDUCE'],
60             DEF_REDUCE => [$REDUCE, undef],
61             SMALL => [$SIZE, 'SMALL'],
62             BIG => [$SIZE, 'BIG'],
63             DEF_BIG => [$SIZE, undef],
64             AUTO => [$AUTO, 'AUTO'],
65             NO_AUTO => [$AUTO, 'NO_AUTO'],
66             DEF_AUTO => [$AUTO, undef],
67             CONVERTED => [$INTERNAL, 'CONVERTED'],
68             IS_REDUCED => [$RED_STATE, 'IS_REDUCED'],
69             );
70              
71             my @DEF_TAG = qw(DEF_MIXED DEF_REDUCE DEF_BIG DEF_AUTO);
72              
73             my $ID = 01;
74              
75             sub new {
76 60     60 0 102 my $proto = shift;
77 60   33     213 my $class = ref($proto) || $proto;
78 60         61 my ($self, @frac, @tags, $tag, $decimal, $p1, $p2, $p3);
79 60 100 100     163 if (&_is_decimal($_[0]) and &_is_decimal($_[1]) and &_is_decimal($_[2]) ) {
    100 100        
    100 100        
    100          
    50          
80 2         5 my $sign = $_[0]/abs($_[0]);
81 2         7 @tags = &_tags(@_[3..$#_]);
82 2         7 ($decimal, $p1, $p2, $p3) = &_fix_num(\@tags, @_[0..2]);
83 2         6 ($p1, $p2, $p3) = (abs($p1),abs($p2),abs($p3) );
84 2         6 @frac = ($p1*$p3+$p2, $sign*$p3);
85 2 50       5 @frac = &_de_decimal(@frac, \@tags) if $decimal;
86             } elsif (&_is_decimal($_[0]) and &_is_decimal($_[1]) ) {
87 46         139 @tags = &_tags(@_[2..$#_]);
88 46         124 ($decimal, @frac) = &_fix_num(\@tags, @_[0..1]);
89 46 100       101 @frac = &_de_decimal(@frac, \@tags) if $decimal;
90 46         85 @frac = &_simplify_sign(@frac);
91             } elsif (&_is_decimal($_[0]) ) {
92             {
93 5         7 @tags = &_tags(@_[1..$#_]);
  5         15  
94 5         16 ($decimal, $p1) = &_fix_num(\@tags, $_[0]);
95 5 100       27 @frac=($p1,1), last if not $decimal;
96 3         11 (@frac[0..1], $tag) = &_from_decimal($p1);
97 3         10 @tags = &_tags(@tags, $tag);
98 3         11 ($decimal,@frac) = &_fix_num(\@tags, @frac);
99 3 50       10 @frac = &_de_decimal(@frac, \@tags) if $decimal;
100             }
101             } elsif ($_[0] =~ /\s*([\+\-]?)\s*([0-9e\.\+\-]+)\s+([0-9e\.\+\-]+)\s*\/\s*([0-9e\.\+\-]+)/) {
102 2         6 my $sign = $1.'1';
103 2         9 @tags = &_tags(@_[1..$#_]);
104 2         8 ($decimal, $p1, $p2, $p3) = &_fix_num(\@tags, $2, $3, $4);
105 2         6 ($p1, $p2, $p3) = (abs($p1),abs($p2),abs($p3) );
106 2         8 @frac = ($p1*$p3+$p2, $sign*$p3);
107 2 50       7 @frac = &_de_decimal($p1*$p3+$p2, $sign*$p3, \@tags) if $decimal;
108             } elsif ($_[0] =~ /\s*([0-9e\.\+\-]+)\s*\/\s*([0-9e\.\+\-]+)/) {
109 5         19 @tags = &_tags(@_[1..$#_]);
110 5         16 ($decimal, @frac) = &_fix_num(\@tags, $1, $2);
111 5 50       15 @frac = &_de_decimal(@frac, \@tags) if $decimal;
112 5         10 @frac = &_simplify_sign(@frac);
113             } else {
114 0         0 croak("\"$_[0]\" is of unknown format");
115             }
116 60 50       204 croak ("Can not have 0 as the denominator") if $frac[1] == 0;
117              
118 60 100 100     1198 if ( &_tag($REDUCE, \@tags) ne 'NO_REDUCE'
119             and &_tag($RED_STATE, \@tags) ne 'IS_REDUCED' )
120             {
121 33         34 my $not_reduced;
122 33         187 ($not_reduced, @frac) = &_reduce(@frac);
123 33 100 100     516 @frac = &_fix_auto('DOWN',\@tags, @frac) if $not_reduced
124             and &_tag($AUTO, \@tags) eq 'AUTO';
125             }
126            
127 60 100       150 @tags[$RED_STATE] = undef if &_tag($RED_STATE, \@tags) eq 'IS_REDUCED';
128              
129 60         150 $self->{'frac'}=\@frac;
130 60         94 $self->{'tags'}=\@tags;
131 60         124 bless ($self, $class);
132 60         513 return $self;
133             }
134              
135             # The following functions are met to be exported as shortcuts to method
136             # operations.
137              
138             sub frac {
139             #special exported function to simplify defining fractions
140 24     24 0 76 return Math::Fraction->new(@_);
141             }
142              
143             # Now are the methodes
144              
145             sub string {
146 43     43 1 59 my $self = shift;
147 43         39 my @frac;
148 43         124 my $mixed = &_tag ($OUTFORMAT, [$_[0]], $self->{'tags'} );
149 43 100       128 if ($mixed eq 'MIXED') {
    50          
    50          
150 23         54 @frac = $self->list('MIXED');
151 23         37 my $string = "";
152 23 100       51 $string .= "$frac[0]" if $frac[0] != 0;
153 23 100 100     599 $string .= " " if $frac[0] != 0 and $frac[1] !=0;
154 23 100       600 $string .= "$frac[1]/$frac[2]" if $frac[1] != 0;
155 23 50       805 $string = "0" if $string eq '';
156 23         527 return $string;
157             } elsif ($mixed eq 'MIXED_RAW') {
158 0         0 @frac = $self->list('MIXED');
159 0         0 return "$frac[0] $frac[1]/$frac[2]";
160             } elsif ($mixed eq 'RAW') {
161 0         0 @frac = $self->list;
162 0 0       0 return ($frac[0] >= 0 ? '+':'')."$frac[0]/$frac[1]";
163             } else {
164 20         44 @frac = $self->list;
165 20         152 return "$frac[0]/$frac[1]";
166             }
167             }
168              
169             sub list {
170 43     43 1 47 my $self = shift;
171 43         36 my @frac = @{$self->{'frac'}};
  43         103  
172 43 100       96 if ($_[0] eq "MIXED") {
173 23         41 my $whole=$frac[0]/$frac[1];
174 23 100       407 $whole=int($whole) if not ref($frac[0]);
175 23         37 $frac[0] = abs($frac[0] - $frac[1]*$whole);
176 23         725 @frac = ($whole, @frac);
177             }
178 43         67 foreach (@frac) {s/^\+//;};
  109         401  
179 43         229 return @frac;
180             }
181              
182             sub reduce {
183 0     0 1 0 my $self = shift;
184 0         0 my ($undef, @frac) = &_reduce(@{$self->{'frac'}});
  0         0  
185 0         0 return Math::Fraction->new(@frac, @{$self->{'tags'}});
  0         0  
186             }
187              
188              
189             sub decimal {
190 2     2 1 4 my $self = shift;
191 2         3 my @frac = @{$self->{'frac'}};
  2         6  
192 2 50       11 return $frac[0]/$frac[1] if not ref($frac[0]);
193 0 0       0 return Math::BigFloat->new(Math::BigFloat::fdiv($frac[0], $frac[1], $DEF{CURRENT}{DIGITS}) ) if ref($frac[0]);
194             }
195              
196             sub num {
197 9     9 1 15 my $self = shift;
198 9         13 my @frac = @{$self->{'frac'}};
  9         26  
199 9 100       45 return $frac[0]/$frac[1] if not ref($frac[0]);
200 8 50       53 return Math::BigFloat->new(Math::BigFloat::fdiv($frac[0], $frac[1], $DEF{CURRENT}{DIGITS}) ) if ref($frac[0]);
201             }
202              
203             ## For the next three methods:
204             # If used on the object use the tags of the object
205             # If given a class use the dafault tags,
206             # .... if a default set is specified then return for that set.
207              
208             sub is_tag {
209 6     6 1 10 my $self = shift;
210 6         7 my $tag = shift;
211 6 50       18 my $default = 1 if $_[0] eq 'INC_DEF';
212 6         7 my $is_tag = 0;
213 6         8 my @tags;
214             {
215 6 50       7 $is_tag = 0, last if not $TAGS{$tag}; #if there is no such tag ret=0
  6         15  
216 6         14 my ($num, $tag) = @{$TAGS{$tag}};
  6         12  
217 6 50       13 if (ref($self) eq "Math::Fraction") {
218 6         6 @tags = @{$self->{'tags'}};
  6         18  
219 6 100       21 $is_tag = 1 , last if $tags[$num] eq $tag;
220 3 50 33     10 $is_tag = undef, last if $tags[$num] eq undef and not $default;
221 3 0 33     8 $is_tag = -1 , last if $DEF{CURRENT}{TAGS}[$num] eq $tag
      33        
222             and $tags[$num] eq undef and $default;
223 3         6 $is_tag = 0;
224             } else {
225 0         0 my $set;
226 0 0       0 $set = 'CURRENT' unless $set = $_[0];
227 0 0       0 $set = 'BLANK' unless exists $DEF{$set};
228 0 0       0 $is_tag = 1 , last if $DEF{$set}{TAGS}[$num] eq $tag;
229 0         0 $is_tag = 0;
230             }
231             }
232 6         42 return $is_tag;
233             }
234              
235             sub tags {
236 5     5 1 6 my $self = shift;
237 5         5 my @tags;
238 5 50       14 if (ref($self) eq "Math::Fraction") {
    50          
239 0 0       0 my $inc_def = 1 if $_[0] eq 'INC_DEF';
240 0         0 @tags = @{$self->{'tags'}}[0..$TAG_END];
  0         0  
241 0         0 my $num;
242 0         0 foreach $num (0 .. $#tags) {
243 0 0 0     0 $tags[$num] = $DEF_TAG[$num] if $tags[$num] eq undef and not $inc_def;
244 0 0 0     0 $tags[$num] = $DEF{CURRENT}{TAGS}[$num] if $tags[$num] eq undef and $inc_def;
245             }
246             } elsif (ref($self) ne "Math::Fraction") {
247 5         5 my $set;
248 5 100       12 $set = 'CURRENT' unless $set = $_[0];
249 5 50       18 $set = 'BLANK' unless exists $DEF{$set};
250 5         6 @tags = @{$DEF{$set}{TAGS}};
  5         14  
251             }
252 5         19 return @tags;
253             }
254              
255             sub digits {
256 5     5 1 5 my $self = shift;
257 5         4 my $set;
258 5 100       9 $set = 'CURRENT' unless $set = $_[0];
259 5 50       9 $set = 'BLANK' unless exists $DEF{$set};
260 5         19 return $DEF{$set}{DIGITS};
261             }
262              
263             ##
264             # These mehods are used form managing default sets.
265              
266             sub sets {
267 0     0 1 0 my $self = shift;
268 0         0 return keys %DEF;
269             }
270              
271             sub name_set {
272 1     1 1 2 shift;
273 1 50       4 return $DEF{CURRENT}{NAME} if not $_[0];
274 1 50       9 $DEF{CURRENT}{NAME} = $_[0] if $_[0];
275             }
276              
277             sub exists_set {
278 1     1 1 10 return exists $DEF{$_[1]};
279             }
280              
281             sub use_set {
282 1     1 1 2 my $self = shift;
283 1         2 my $name = shift;
284 1 50 33     8 if (exists $DEF{$name} and not $DEF{$name}{READONLY}) {
285 1         2 $DEF{CURRENT} = $DEF{$name};
286 1         8 return $name;
287             } else {
288 0         0 return undef;
289             }
290             }
291              
292             sub temp_set {
293 2     2 1 3 my $self = shift;
294 2         4 my $name = shift;
295 2 100       4 if (not $name) {
296 1         2 $ID++;
297 1         4 $name = "\cI\cD$ID";
298 1         3 $self->copy_set('CURRENT', $name);
299 1         3 $self->copy_set('DEFAULT', 'CURRENT');
300 1         8 return $name;
301             } else { #if $name;
302 1         4 my $return = $self->copy_set($name, 'CURRENT');
303 1         4 $self->del_set($name);
304 1         7 return $return
305             }
306             }
307              
308              
309             sub load_set {
310 3     3 1 4 my $self = shift;
311 3 50       8 if (exists $DEF{$_[0]}) {
312 3 50       14 $self->copy_set($_[0],'CURRENT') if exists $DEF{$_[0]};
313 3         17 return $_[0]
314             } else {
315 0         0 return undef;
316             }
317             }
318              
319             sub save_set {
320 1     1 1 2 my $self = shift;
321 1         2 my $name;
322 1 50       4 $name = $DEF{CURRENT}{NAME} unless $name = shift;
323 1 50 33     13 ++$ID, $name = "\cI\cD:$ID" if not $name or $name eq 'RAND';
324 1   50     3 return $self->copy_set('CURRENT', $name) && $name;
325             }
326              
327             sub copy_set {
328 7     7 1 8 shift;
329 7         10 my ($name1, $name2) = @_;
330 7 50 33     51 if ($DEF{$name2}{READONLY} or $name2 eq 'BLANK' or not exists $DEF{$name1}) {
      33        
331 0         0 return 0;
332             } else {
333 7         13 $DEF{$name2} = {}; # kill any links from use;
334 7         16 $DEF{$name2}{TAGS} = [@{$DEF{$name1}{TAGS}}];
  7         27  
335 7         15 $DEF{$name2}{DIGITS} = $DEF{$name1}{DIGITS};
336 7 100       15 $DEF{$name2}{NAME} = $name2 unless $name2 eq 'CURRENT';
337 7 100       17 $DEF{$name2}{NAME} = $name1 if $name2 eq 'CURRENT';
338 7         15 return 1;
339             }
340             }
341              
342             sub del_set {
343 1 50 33 1 1 10 if (exists $DEF{$_[1]} and not $DEF{$_[1]}{SYSTEM}) {
344 1         4 delete $DEF{$_[1]};
345 1         2 return $_[1];
346             }
347             }
348              
349             # All of the modify methods are not meant to return anything, they modify
350             # the object being referenced too.
351              
352             sub modify {
353             # This method works almost like the new method except that it takes an
354             # object as an argement and will modify it instead of creating a new
355             # object, also any tags assosated with the object are left in tact
356             # unless a new tag is given to override the old.
357              
358 0     0 1 0 my $me = shift;
359 0         0 my $self;
360 0         0 my @tags = @{$me->{'tags'}};
  0         0  
361 0         0 $self = Math::Fraction->new(@_, @tags, @_); # The extra @_ is their to override tags
362 0         0 $me->{'frac'} = $self->{'frac'};
363 0         0 $me->{'tags'} = $self->{'tags'};
364             }
365              
366             sub modify_digits {
367 6     6 0 10 my $self = shift;
368 6         45 $DEF{CURRENT}{DIGITS} = shift;
369             }
370              
371             sub modify_reduce {
372 0     0 1 0 my $me = shift;
373 0         0 my $self = $me->reduce;
374 0         0 $me->{'frac'} = $self->{'frac'};
375 0         0 $me->{'tags'} = $self->{'tags'};
376             }
377              
378              
379             sub modify_num {
380 0     0 1 0 my $self = shift;
381 0         0 $self->[0] = $_[0]
382             }
383              
384             sub modify_den {
385 0     0 1 0 my $self = shift;
386 0         0 $self->[1] = $_[0]
387             }
388              
389             sub modify_tag {
390 7     7 1 12 my $self = shift;
391 7         8 my ($return, @return);
392 0         0 my $newtag;
393 7         13 foreach $newtag (@_) {
394 9         21 my $tagnum = &_tagnum($newtag);
395 9 50       44 if ($tagnum == -1) {
    100          
396 0         0 push @return, undef;
397             } elsif (ref($self) eq "Math::Fraction") {
398 3         3 my @frac = @{$self->{'frac'}};
  3         10  
399 3         3 my @tags = @{$self->{'tags'}};
  3         8  
400 3         9 my @newtags = &_tags(@tags,$newtag);
401             # Now transform the Fraction based on the new tag.
402 3 100       14 if ($tagnum == $SIZE) {
    50          
403 1         37 my $newtag = &_tag($SIZE, \@newtags);
404 1 50       5 @frac = map { "$_"+0 } @frac if $newtag eq 'SMALL';
  0         0  
405 1 50       5 @frac = map { Math::BigInt->new($_) } @frac if $newtag eq 'BIG';
  2         48  
406             } elsif ($tagnum == $REDUCE) {
407 0 0       0 (undef, @frac) = &_reduce(@frac) if &_tag($REDUCE, \@newtags) eq 'REDUCE';
408             }
409             # Finally Modify the Fraction
410 3         38 $self->{'frac'} = \@frac;
411 3         8 $self->{'tags'} = \@newtags;
412             } else {
413 6         15 $DEF{CURRENT}{TAGS}[$tagnum] = $newtag;
414             }
415 9         24 push @return, $newtag;
416             }
417 7         86 return @return;
418             }
419            
420             # These methods are meant to be called with the overload operators.
421              
422             sub add {
423 16     16 0 19 my @frac1 = @{$_[0]->{'frac'}};
  16         45  
424 16         21 my @tags1 = @{$_[0]->{'tags'}};
  16         62  
425 16         18 my (@frac2, @frac, @tags2, $frac);
426 16         20 my $skipauto = 0;
427 16 100       40 @frac2 = @{$_[1]->{'frac'}}, @tags2 = @{$_[1]->{'tags'}} if ref($_[1]) eq "Math::Fraction";
  11         37  
  11         61  
428 16 100       73 @frac2 = &_from_decimal($_[1]), $tags2[$INTERNAL] = 'CONVERTED' if ref($_[1]) ne "Math::Fraction";
429 16         68 my @tags = &_tags_preserve([@tags1],[@tags2]);
430              
431             LOOP: {
432 16 100       35 if (&_tag($REDUCE, \@tags) eq 'NO_REDUCE') {
  17         35  
433 2         9 @frac = ($frac1[0]*$frac2[1]+$frac2[0]*$frac1[1],$frac1[1]*$frac2[1]);
434             } else {
435             # Taken from Knuth v2 (rev 2), p313.
436             # It will always return a reduced fraction.
437 15         27 my $gcd1 = &_gcd($frac1[1],$frac2[1]);
438 15         225 my $tmp = $frac1[0]*($frac2[1]/$gcd1) + $frac2[0]*($frac1[1]/$gcd1);
439 15         1720 my $gcd2 = &_gcd($tmp,$gcd1);
440 15         43 @frac = ( $tmp/$gcd2, ($frac1[1]/$gcd1)*($frac2[1]/$gcd2) );
441 15         896 $tags[$RED_STATE] = 'IS_REDUCED';
442             }
443 17 100 66     37 if ( (&_tag($AUTO, \@tags) eq 'AUTO') and (not $skipauto) and
      100        
      66        
      66        
444             ($tags[$SIZE] eq 'SMALL') and ($frac[0]=~/[eE]/ or $frac[1]=~/[eE]/) )
445             {
446 1         3 (@frac1[0..1], @frac2[0..1]) = map { Math::BigInt->new($_) } (@frac1, @frac2);
  4         183  
447 1         105 $tags[$SIZE] = 'BIG';
448 1         3 $skipauto = 1;
449 1         2 redo LOOP;
450             }
451             }
452 16         52 return Math::Fraction->new(@frac, @tags);
453             }
454              
455             sub sub {
456 0     0 0 0 my ($frac1, $frac2) = ($_[$_[2]], $_[not $_[2]]); # swap if needed
457 0 0       0 $frac1 = Math::Fraction->new($frac1, 'CONVERTED') if ref($frac1) ne "Math::Fraction";
458 0 0       0 $frac2 = Math::Fraction->new($frac2, 'CONVERTED') if ref($frac2) ne "Math::Fraction";
459              
460 0         0 $frac2 = Math::Fraction->new($frac2->{'frac'}[0], -$frac2->{'frac'}[1], @{$frac2->{'tags'}});
  0         0  
461              
462 0         0 return $frac1 + $frac2;
463             }
464              
465             sub mul {
466 6     6 0 8 my @frac1 = @{$_[0]{'frac'}};
  6         20  
467 6         11 my @tags1 = @{$_[0]{'tags'}};
  6         17  
468 6         9 my (@frac2, @frac, @tags2);
469 6 100       16 @frac2 = @{$_[1]->{'frac'}}, @tags2 = @{$_[1]->{'tags'}} if ref($_[1]) eq "Math::Fraction";
  2         8  
  2         4  
470 6 100       25 @frac2 = (&_from_decimal($_[1])), $tags2[$INTERNAL] = 'CONVERTED' if ref($_[1]) ne "Math::Fraction";
471 6         23 my @tags = &_tags_preserve([@tags1],[@tags2]);
472 6         18 my $skipauto = 0;
473             LOOP: {
474 6 50       7 if (&_tag($REDUCE, \@tags) eq 'NO_REDUCE') {
  7         15  
475 0         0 @frac = ($frac1[0]*$frac2[0],$frac1[1]*$frac2[1]);
476             } else {
477 7         20 my($gcd1, $gcd2)=(&_gcd($frac1[0],$frac2[1]),&_gcd($frac2[0],$frac1[1]));
478 7         27 $frac[0] = ($frac1[0]/$gcd1)*($frac2[0]/$gcd2);
479 7         635 $frac[1] = ($frac1[1]/$gcd2)*($frac2[1]/$gcd1);
480 7         554 $tags[$RED_STATE] = 'IS_REDUCED';
481             }
482 7 100 66     17 if ( (&_tag($AUTO, \@tags) eq 'AUTO') and (not $skipauto) and
      100        
      66        
      66        
483             ($tags[$SIZE] eq 'SMALL') and ($frac[0]=~/[eE]/ or $frac[1]=~/[eE]/) )
484             {
485 1         3 (@frac1[0..1], @frac2[0..1]) = map { Math::BigInt->new($_) } (@frac1, @frac2);
  4         82  
486 1         25 $tags[$SIZE] = 'BIG';
487 1         2 $skipauto = 1;
488 1         2 redo LOOP;
489             }
490             }
491 6         25 return Math::Fraction->new(@frac, @tags);
492             }
493              
494             sub div {
495 0     0 0 0 my ($frac1, $frac2) = ($_[$_[2]], $_[not $_[2]]); # swap if needed
496 0 0       0 $frac1 = Math::Fraction->new($frac1, 'CONVERTED') if ref($frac1) ne "Math::Fraction";
497 0 0       0 $frac2 = Math::Fraction->new($frac2, 'CONVERTED') if ref($frac2) ne "Math::Fraction";
498              
499 0         0 $frac2 = Math::Fraction->new($frac2->{'frac'}[1], $frac2->{'frac'}[0], @{$frac2->{'tags'}});
  0         0  
500             #Makes a copy of the fraction with the num and den switched.
501              
502 0         0 return $frac1 * $frac2;
503             }
504              
505             sub pow {
506 1     1 0 1 my (@frac, @frac1, @tags1);
507 1 50       5 @frac1 = @{$_[$_[2]]->{'frac'}}, @tags1 = @{$_[$_[2]]->{'tags'}} if ref($_[$_[2]]) eq "Math::Fraction";
  1         4  
  1         3  
508 1 50       4 @frac1 = &_from_decimal($_[$_[2]]) if ref($_[$_[2]]) ne "Math::Fraction";
509 1         1 my $frac2;
510 1 50       5 $frac2 = $_[not $_[2]]->decimal if ref($_[not $_[2]]) eq "Math::Fraction";
511 1 50       5 $frac2 = $_[not $_[2]] if ref($_[not $_[2]]) ne "Math::Fraction";
512 1         2 my @tags = @tags1;
513 1         10 my $skipauto = 0;
514              
515 1         25 LOOP: {
516 1         2 @frac = ($frac1[0]**$frac2,$frac1[1]**$frac2);
517              
518 1 50 33     6 if ( (&_tag($AUTO, \@tags) eq 'AUTO') and (not $skipauto) and
      33        
      33        
      33        
519             ($tags[$SIZE] eq 'SMALL') and ($frac[0]=~/[eE]/ or $frac[1]=~/[eE]/) )
520             {
521 0         0 @frac1 = map { Math::BigInt->new($_) } @frac1;
  0         0  
522 0         0 $tags[$SIZE] = 'BIG';
523 0         0 $skipauto = 1;
524 0         0 redo LOOP;
525             }
526             }
527              
528 1         4 return Math::Fraction->new(@frac, @tags);
529             }
530              
531             sub sqrt {
532 0     0 0 0 my $self = shift;
533 0         0 my @frac = @{$self->{'frac'}};
  0         0  
534 0         0 my @tags = @{$self->{'tags'}};
  0         0  
535 0         0 my $ans;
536 0 0       0 if ( ref($frac[0]) ) {
537 0         0 $frac[0] = Math::BigFloat->new( Math::BigFloat::fsqrt($frac[0], $DEF{CURRENT}{DIGITS}) );
538 0         0 $frac[1] = Math::BigFloat->new( Math::BigFloat::fsqrt($frac[1], $DEF{CURRENT}{DIGITS}) );
539             } else {
540 0         0 @frac = (sqrt($frac[0]) , sqrt($frac[1]));
541             }
542 0         0 return Math::Fraction->new(@frac, @tags);
543             }
544              
545              
546             sub abs {
547 0     0 0 0 my $self = shift;
548 0         0 my @frac = @{$self->{'frac'}};
  0         0  
549 0         0 my @tags = @{$self->{'tags'}};
  0         0  
550 0         0 return Math::Fraction->new(abs($frac[0]),abs($frac[1]),@tags,'IS_REDUCED');
551             }
552              
553             sub cmp {
554 0     0 0 0 my @frac1 = @{$_[0]->{'frac'}};
  0         0  
555 0         0 my @tags1 = @{$_[0]->{'tags'}};
  0         0  
556 0         0 my (@frac2, @frac, @tags2, $x, $y);
557 0 0       0 @frac2 = @{$_[1]->{'frac'}}, @tags2 = @{$_[1]->{'tags'}} if ref($_[1]) eq "Math::Fraction";
  0         0  
  0         0  
558 0 0       0 @frac2 = &_from_decimal($_[1]), @tags2 = qw(CONVERTED) if ref($_[1]) ne "Math::Fraction";
559 0         0 my @tags = &_tags_preserve([@tags1],[@tags2]);
560 0 0       0 if (&_tag($REDUCE, \@tags) == 'NO_REDUCE') {
561 0         0 $x = $frac1[0]*$frac2[1];
562 0         0 $y = $frac2[0]*$frac1[1];
563             } else {
564 0         0 my $gcd1 = &_gcd($frac1[1],$frac2[1]);
565 0         0 $x = $frac1[0]*($frac2[1]/$gcd1);
566 0         0 $y = $frac2[0]*($frac1[1]/$gcd1);
567             }
568 0         0 return $x <=> $y;
569             }
570              
571             # These function are that functions and not ment to be used as methods
572              
573             sub _fix_num {
574 63     63   74 my $tagsref = shift;
575 63         122 my @return = @_;
576 63         112 my $auto = &_tag($AUTO, $tagsref) eq 'AUTO';
577 63         118 $tagsref->[$SIZE] = &_tag($SIZE, $tagsref);
578 63 100       136 $tagsref->[$SIZE] = 'SMALL' if $auto;
579 63         119 my $num;
580 63         113 my $decimal = 0;
581 63         83 foreach $num (@return) {
582 125 50       542 if (ref($num) eq "Math::BigFloat") {
    100          
    50          
    100          
583 0 0       0 $tagsref->[$SIZE] = 'BIG' unless $auto;
584 0         0 $decimal = 1;
585             } elsif (ref($num) eq "Math::BigInt") {
586 12 50       25 $tagsref->[$SIZE] = 'BIG' unless $auto;
587             } elsif (ref($num)) {
588             # do nothing
589             } elsif ($num =~ /[\.\e\E]/) {
590 5         7 $decimal = 1;
591             }
592 125 100       361 if ($auto) {
593 123         444 $num =~ /[\+\-]?\s*0*([0-9]*)\s*\.?\s*([0-9]*)0*/;
594 123         643 my $length = length($1)+length($2);
595 123 100       408 $tagsref->[$SIZE] = 'BIG' if $length > 15;
596             }
597             }
598 63 100       175 if ($tagsref->[$SIZE] eq 'BIG') {
599 10 100       29 @return = map {Math::BigInt->new("$_")} @return if not $decimal;
  18         593  
600 10 100       408 @return = map {Math::BigFloat->new("$_")} @return if $decimal;
  1         9  
601             }
602 63 100 66     708 if ($tagsref->[$SIZE] eq 'SMALL' and $auto) {
603 53         81 @return = map {"$_"+0} @return;
  106         402  
604             }
605 63         351 return ($decimal, @return);
606             }
607              
608             sub _fix_auto {
609 8     8   10 my $direction = shift;
610 8         11 my $tagsref = shift;
611 8         15 my @return = @_;
612 8         10 $tagsref->[$SIZE] = 'SMALL';
613 8         10 my $num;
614 8         13 foreach $num (@return) {
615 16         91 $num =~ /[\+\-]?\s*0*([0-9]*)\s*\.?\s*([0-9]*)0*/;
616 16         133 my $length = length($1)+length($2);
617 16 100       50 $tagsref->[$SIZE] = 'BIG' if $length > 15;
618             }
619 8 50 66     43 if ($tagsref->[$SIZE] eq 'BIG' and $direction eq 'BOTH') {
    100          
620 0         0 @return = map {Math::BigInt->new("$_")} @return;
  0         0  
621             } elsif ($tagsref->[$SIZE] eq 'SMALL') {
622 6         8 @return = map {"$_"+0} @return;
  12         61  
623             }
624 8         23 return (@return);
625             }
626              
627             sub _is_decimal {
628 282     282   901 my $return = $_[0] =~ /^\s*[\+\-0-9eE\.]+\s*$/;
629 282         1827 return $return;
630             }
631              
632             sub _reduce {
633 33     33   73 my @frac = @_;
634 33         62 my $gcd = &_gcd(@frac);
635 33 100       60 if ($gcd == 1 ) {
636 24         178 return (0, @frac)
637             } else {
638 9         256 return (1, $frac[0]/$gcd, $frac[1]/$gcd);
639             }
640             }
641              
642             sub _simplify_sign {
643 51     51   80 my @frac = @_;
644 51         52 my $sign = 1;
645 51 100       149 $sign = ($frac[0]/abs($frac[0]))*($frac[1]/abs($frac[1])) if $frac[0];
646 51         2690 @frac = ($sign*abs($frac[0]), abs($frac[1]) );
647 51         1004 return @frac;
648             }
649              
650             sub _tags {
651 66     66   114 my @return = (undef, undef);
652 66         84 my ($NUM, $VALUE) = (0, 1);
653              
654 66         105 foreach (@_) {
655 181 100       396 next if not $TAGS{$_};
656 73         69 my ($num, $value) = @{$TAGS{$_}};
  73         177  
657 73         382 $return[$num] = $value;
658             }
659              
660 66         206 return @return;
661             }
662              
663              
664             sub _tag {
665 402     402   472 my $item = shift;
666 402         459 my $return;
667             my $ref;
668 402         813 foreach $ref (@_, $DEF{CURRENT}{TAGS}) {
669 752 100       683 last if $return = ${$ref}[$item];
  752         1863  
670             }
671 402         1498 return $return
672             }
673              
674             sub _tagnum {
675 9     9   9 my $item = shift;
676 9 50       23 if (exists $TAGS{$item}) {
677 9         21 return $TAGS{$item}[0];
678             } else {
679 0         0 return -1;
680             }
681             }
682              
683             sub _tags_preserve {
684 22     22   23 my @tags1 = @{$_[0]};
  22         47  
685 22         23 my @tags2 = @{$_[1]};
  22         56  
686 22         28 my @tags;
687 22 50       65 if ($tags1[$INTERNAL] eq 'CONVERTED') {
    100          
688 0         0 @tags = @tags2;
689             } elsif ($tags2[$INTERNAL] eq 'CONVERTED') {
690 9         18 @tags = @tags1;
691             } else {
692 13 100       24 @tags = map {$tags1[$_] eq $tags2[$_] and $tags1[$_]} (0 .. $#tags1) ;
  51         235  
693             }
694 22         76 return @tags;
695             }
696              
697             sub _gcd {
698             # Using Euclid's method found in Knuth v2 (rev 2) p320 brought to my
699             # attention from the BigInt module
700              
701 77     77   138 my ($x, $y) = (abs($_[0]), abs($_[1]));
702 77 100       540 if ( ref($x) ) {
703 12         36 $x = Math::BigInt->new( $x->bgcd($y) );
704             } else {
705             {
706 65 100       61 $x=1, last if $y > 1e17; # If this is so % will thinks its a zero so if
  65         157  
707             # $y>1e17 will simply will basicly give up and
708             # have it return 1 as the GCD.
709 63         63 my ($x0);
710 63         207 while ($y != 0) {
711 240         212 $x0 = $x;
712 240         348 ($x, $y) = ($y, $x % $y);
713             # Note $x0 = $x, $x = $y, $y= $x % $y Before the Swith
714 240 100 66     1319 $x=1, last if ($x0>99999999 or $x>999999999) and int($x0/$x)*$x+$y != $x0;
      100        
715             # This is to see if the mod operater through up on us when dealing with
716             # large numbers. If it did set the gcd = 1 and quit.
717             }
718             }
719             }
720 77         10677 return $x;
721             }
722              
723             sub _de_decimal {
724 1     1   3 my @frac = @_;
725 1         2 my @return;
726 1         4 my $big = &_tag($SIZE, $_[2]);
727 1         3 my (@int_part, @decimal_part);
728 1 50       4 if ($big eq "BIG") {
729 0         0 my @digits = (1,1);
730 0         0 ($int_part[0], $digits[0]) = $frac[0]->fnorm =~ /(\d+)E\-(\d+)/;
731 0         0 ($int_part[1], $digits[1]) = $frac[1]->fnorm =~ /(\d+)E\-(\d+)/;
732 0         0 @digits = sort {$a <=> $b} @digits;
  0         0  
733 0         0 my $factor = 10**$digits[1];
734 0         0 @frac = (($_[0]*$factor),($_[1]*$factor));
735 0         0 chop $frac[0]; chop $frac[1];
  0         0  
736 0         0 @frac = (Math::BigInt->new($frac[0]), Math::BigInt->new($frac[1]) );
737             } else {
738 1         11 ($int_part[0], $decimal_part[0]) = $frac[0] =~ /(\d+)\.(\d+)/;
739 1         8 ($int_part[1], $decimal_part[1]) = $frac[1] =~ /(\d+)\.(\d+)/;
740 1         8 @decimal_part = sort {$a <=> $b} (length($decimal_part[0]),length($decimal_part[1]) );
  1         7  
741 1         2 my $factor = 10**$decimal_part[1];
742 1         4 @frac = ($_[0]*$factor, $_[1]*$factor);
743             }
744 1         3 return @frac;
745             }
746              
747             sub _from_decimal {
748 12     12   23 my $decimal = shift; # the decimal (1.312671267127)
749 12 100       117 my $big = 'BIG' if ref($decimal);
750 12         18 my ($repeat); # flag to keep track if it is repeating or not
751             my ($sign);
752 0         0 my ($factor, $int_factor);
753 0         0 my ($factor2);
754 0         0 my ($whole_num, $whole_num_len);
755 0         0 my ($int_part); # integer part (1)
756 0         0 my ($decimal_part, $decimal_part_len); # decimal part (312671267127)
757 0         0 my ($decimal_part2); # decimal part - last bit \/ (312671267)
758 0         0 my ($pat, $pat_len); # repeating pat (1267)
759 0         0 my ($pat_lastb); # last bit of repeating pat (127)
760 0         0 my ($beg_part, $beg_part_len); # non-repeating part (3)
761 0         0 my ($other_part, $other_part_len); # repeating part (1267126712127)
762 0         0 my ($frac1, $frac2, $frac3);
763              
764 12         68 my $rnd_mode = $Math::BigFloat::rnd_mode; # to avoid problems with incon.
765 12         81 $Math::BigFloat::rnd_mode = 'trunc'; # rounding
766              
767 12         238 $decimal = "$decimal";
768 12         137 $decimal =~ s/\s//g;
769 12         44 ($sign, $int_part, $decimal_part) = $decimal =~ /([\+\-]?)\s*(\d*)\.(\d+)$/;
770 12         20 $sign .= '1';
771 12         16 $decimal_part_len = length($decimal_part);
772 12 100       30 $int_part = "" unless $int_part;
773 12         27 $factor = '1'.'0'x(length($decimal_part));
774 12 100       43 $factor = Math::BigFloat->new($factor) if $big;
775             # Make it a BigFloat now to simplfy latter
776 12         287 $int_factor = '1'.'0'x(length($int_part));
777 12         14 $beg_part_len = 0;
778             OuterBlock:
779 12         32 while ($beg_part_len < $decimal_part_len) {
780 34         57 $beg_part = substr($decimal_part, 0, $beg_part_len);
781 34         51 $other_part = substr($decimal_part, $beg_part_len);
782 34         30 $other_part_len = length($other_part);
783 34         24 my $i;
784 34         79 for ($i = 1; $i < ($other_part_len/2+1); $i++) {
785 582         672 $pat = substr($other_part, 0, $i);
786 582         518 $pat_len = $i;
787 582         566 local $_ = $other_part;
788 582         496 $repeat = undef;
789 582         479 while (1) {
790 1243         6917 ($_) = /^$pat(.*)/;
791 1243         1375 my $length = length($_);
792              
793 1243 100       2509 if ( $length <= $pat_len) {
794 612 100       2004 last unless $length;
795 34         41 $pat_lastb = substr($pat, 0, $length);
796 34 100       62 $repeat=1 ,last OuterBlock if $pat_lastb eq $_;
797 32 100       152 if ($pat_lastb eq $_ - 1) {
798             # this is needed to see if it really is the repeating fracton
799             # we intented it to be. If we don't do this 1.1212 would become
800             # 1120/999 = 1.1211211211.
801             # The first three lines converts it to a fraction and the
802             # rests tests it to the actual repeating decimal/
803             # The NO_REDUCE flag is their to save time as reducing large
804             # fraction can take a bit of time which is unnecessary as we will
805             # be converting it to a decimal.
806 2         4 $decimal_part2 = substr($decimal_part, 0, $decimal_part_len - length($pat_lastb));
807 2         6 $factor2 = '1'.'0'x(length($decimal_part2));
808 2         25 $frac1 = Math::Fraction->new('0'.$beg_part,"1"."0"x$beg_part_len, 'NO_REDUCE', $big);
809 2         1112 $frac2 = Math::Fraction->new('0'.$pat,"9"x$pat_len."0"x$beg_part_len, 'NO_REDUCE', $big);
810 2         17 $frac3 = $frac1 + $frac2;
811 2         9 my $what_i_get = $frac3->decimal;
812 2         14 my $places = length($what_i_get);
813 2 50       7 my $decimal_p_tmp = $decimal_part2 if not $big;
814 2 50       5 $decimal_p_tmp = Math::BigFloat->new($decimal_part2) if $big;
815 2         12 my $what_i_should_get = (($decimal_p_tmp)/$factor2)."$pat"x($places);
816             # The rest of this is doing nothing more but trying to compare
817             # the what_i_get and what_i_should_get but becuse the stupid
818             # BigFloat module is so pragmentic all this hopla is nessary
819 2 50       6 $what_i_should_get = Math::BigFloat->new($what_i_should_get) if $big;
820 2 50       5 $what_i_should_get = $what_i_should_get->fround(length($what_i_get)-1) if $big;
821 2 50       5 $what_i_should_get = Math::BigFloat->new($what_i_should_get) if $big;
822             # ^^ Needed because the dam fround method does not return a
823             # BigFloat object!!!!!!
824 2 50       5 my $pass = "$what_i_get" eq "$what_i_should_get" if $big;
825 2 50       10 $pass = $what_i_get == $what_i_should_get if not $big;
826 2 50       16 $repeat=1, last OuterBlock if ($pass);
827             }
828             }
829             }
830             }
831 30         52 $beg_part_len++;
832             }
833 12 100       21 if ($repeat) {
834 4         29 $frac1 = Math::Fraction->new('0'.$beg_part,"1"."0"x$beg_part_len, $big);
835 4         25 $frac2 = Math::Fraction->new('0'.$pat,"9"x$pat_len."0"x$beg_part_len, $big);
836 4 100       17 $int_part = Math::Fraction->new('0'.$int_part, 1, 'BIG') if $big;
837 4         14 $frac3 = $sign*($int_part + $frac1 + $frac2);
838 4         21 return @{$frac3->{'frac'}};
  4         40  
839             } else {
840 8         44 return ($decimal*$factor, $factor, $big);
841             }
842 0           $Math::BigFloat::rnd_mode = $rnd_mode; # set it back to what it was.
843             }
844              
845             1;
846              
847             __END__