# # Fraction object # Keeps track of two variables- numerator and denominator. # Has subroutines for basic arithmatic functions, for anything # more complicated, it can return a scalar value of # numerator/denominator. # VS 7/20/2000 =head3 Fraction This object is designed to ease the use of fractions =head4 Variables and Methods Variables numerator #numerator of fraction denominator #denominator of fraction Arithmetic Methods #these will all accept a scalar value or #another fraction as an argument plus #returns the sum of the fraction and argument minus #returns fraction minus argument subtractFrom #returns argument minus fraction divBy #returns fraction divided by argument divInto #returns argument divided by fraction times #returns fraction times argument compare #returns <, =, or > for the relation of fraction to argument pow #returns fraction raised to argument, a given integer power Other methods reduce #reduces to lowest terms, and makes sure denominator is positive scalar #returns the scalar value numerator/denominator print #prints the fraction print_mixed #prints the fractionas a mixed number print_inline #prints the fraction like this 2/3 =head4 Synopsis The fraction object stores two variables, numerator and denominator. The basic arithmatic methods listed above can be performed on a fraction, and it can return its own scalar value for use with functions expecting a scalar (ie, sqrt($frac->scalar) ). =cut BEGIN { be_strict(); } package Fraction; my %fields = ( numerator => undef, denominator => undef, ); sub new { my $class = shift; my @input = @_; my $num; my $denom; unless (@_ == 1 or @_ == 2) { warn "Invalid number of arguments to create new Fraction. Use the form new Fraction(numerator, denominator) or new Fraction(value) to send a single scalar."; } # if we've been given a scalar as input: # this will ensure that the numerator is a whole number. If it is not, this will # multiply by 10 until it is a whole number, keeping track of the appropriate denominator. # The loop conditional checks that the difference between the number and its int value # is less than .000000001, NOT that they are equal. Because of imprecisions with floating # point numbers, checking for equality will NOT work in many cases. if (@_ == 1) { my $tempDenom = 1; while ($input[0] - int($input[0]) > .000000001) {$input[0] *= 10; $tempDenom *= 10;} $num = $input[0]; $denom = $tempDenom; } else { $num = $input[0]; $denom = $input[1]; } my $self = { _permitted => \%fields, numerator => $num, denominator => $denom, }; bless $self, $class; return $self; } ########################## # Access methods ########################## sub numerator { my $self = shift; my $type = ref($self) || die "$self is not an object"; unless (exists $self->{numerator} ) { die "Can't find numerator field in object of class $type"; } if (@_) { return $self->{numerator} = shift; } else { return $self->{numerator} } } sub denominator { my $self = shift; my $type = ref($self) || die "$self is not an object"; unless (exists $self->{denominator} ) { die "Can't find denominator field in object of class $type"; } if (@_) { return $self->{denominator} = shift; } else { return $self->{denominator} } } sub DESTROY { # doing nothing about destruction, hope that isn't dangerous } ################################################################################### # Basic Arithmetic Methods # Each returns a new Fraction appropriate to the operation sub plus { my $self = shift; my $input = shift; $input = new Fraction($input*100, 100) unless (ref($input) eq "Fraction"); my $lcm = $self->lcm($self->{denominator}, $input->{denominator}); my $scaleA = $lcm/$self->{denominator}; my $scaleB = $lcm/$input->{denominator}; my $num = $self->{numerator}*$scaleA + $input->{numerator}*$scaleB; my $frac = new Fraction($num, $lcm); $frac->reduce; $frac; } sub minus { my $self = shift; my $input = shift; $input = new Fraction($input*100, 100) unless (ref($input) eq "Fraction"); my $lcm = $self->lcm($self->{denominator}, $input->{denominator}); my $scaleA = $lcm/$self->{denominator}; my $scaleB = $lcm/$input->{denominator}; my $num = $self->{numerator}*$scaleA - $input->{numerator}*$scaleB; my $frac = new Fraction($num, $lcm); $frac->reduce; $frac; } sub subtractFrom { my $self = shift; my $input = shift; $input = new Fraction($input*100, 100) unless (ref($input) eq "Fraction"); my $lcm = $self->lcm($self->{denominator}, $input->{denominator}); my $scaleA = $lcm/$self->{denominator}; my $scaleB = $lcm/$input->{denominator}; my $num = $input->{numerator}*$scaleB - $self->{numerator}*$scaleA; my $frac = new Fraction($num, $lcm); $frac->reduce; $frac; } sub divInto { my $self = shift; my $input = shift; $input = new Fraction($input*100, 100) unless (ref($input) eq "Fraction"); my $num = $input->{numerator}*$self->{denominator}; my $denom = $input->{denominator}*$self->{numerator}; my $frac = new Fraction($num, $denom); $frac->reduce; $frac; } sub divBy { my $self = shift; my $input = shift; $input = new Fraction($input*100, 100) unless (ref($input) eq "Fraction"); my $num = $self->{numerator}*$input->{denominator}; my $denom = $input->{numerator}*$self->{denominator}; my $frac = new Fraction($num, $denom); $frac->reduce; $frac; } sub times { my $self = shift; my $input = shift; $input = new Fraction($input*100, 100) unless (ref($input) eq "Fraction"); my $num = $self->{numerator}*$input->{numerator}; my $denom = $self->{denominator}*$input->{denominator}; my $frac = new Fraction($num, $denom); $frac->reduce; $frac; } sub pow { my $self = shift; my $input = shift; if($input == 0) { # 0 power, always return 1 if($self->{numerator} == 0) { warn "Indeterminant form, 0^0, in Fraction power"; } return new Fraction(1,0); } my ($n, $d); if($input<0) { $d = $self->{numerator}; $n = $self->{denominator}; if($d==0) { warn "Computing 1/0 in Fraction"; } $input = -$input; } else { $n = $self->{numerator}; $d = $self->{denominator}; } my $g = $self->gcd($n, $d); if($d<0) {$g = -$g;} $n /= $g; $d /= $g; return new Fraction($n**$input, $d**$input); } ######################################################################### # Other User-Accessed Methods # returns a string denoting relation-- < = or > # a string is returned for ease of use in writing problems sub compare { my $self = shift; my $input = shift; $input = $input->scalar if (ref($input) eq "Fraction"); my $relation = undef; $relation = "<" if ($self->scalar < $input); $relation = "=" if ($self->scalar == $input); $relation = ">" if ($self->scalar > $input); $relation; } # returns the scalar value of numerator/denominator sub scalar { my $self = shift; my $scalar = $self->{numerator}/$self->{denominator}; $scalar; } # reduces a fraction to lowest terms, and makes denominator positive sub reduce { my $self = shift; my $gcd = $self->gcd($self->{numerator}, $self->{denominator}); if($self->{denominator}<0) {$gcd = -$gcd;} $self->{numerator} = $self->{numerator}/$gcd; $self->{denominator} = $self->{denominator}/$gcd; } # standard print method. Outputs string containing fraction displayed (in math mode # if needed). sub print { my $self = shift; my $out; # if it's a whole number, just print the number if ($self->{denominator} == 1) { $out = $self->{numerator}; } # positive fraction: print out in plain math mode elsif ($self->scalar > 0) { $out = " \\frac{$self->{numerator}}{$self->{denominator}} "; } # negative fraction: print out negative sign and then absolute value in # fraction form, avoiding parenthesis around the negative portion. else { my $foo = -$self->{numerator}; $out = " -\\frac{$foo}{$self->{denominator}} "; } $out; } # forces printing of a mixed number, if applicable. sub print_mixed { my $self = shift; my $out; # if it's not an improper, just pass on to the regular print method if ($self->{numerator} < $self->{denominator} ) { $out = $self->print; } # otherwise print out the mixed number strong. This does not alter the # actual value of the fraction in any way. else { my $tempNum = $self->{numerator}; my $tempDenom = $self->{denominator}; my $coeff = int($tempNum/$tempDenom); $tempNum = $tempNum % $tempDenom; $out = " -$coeff \\frac{abs($tempNum)}{abs($tempDenom)} " if ($self->scalar < 0); $out = " $coeff \\frac{$tempNum}{$tempDenom} " if ($self->scalar > 0); $out = $coeff if ($tempNum == 0); } $out; } # prints fraction as 4 or 5/3 as needed sub print_inline { my $self = shift; my $out; # if it's a whole number, just print the number if ($self->{denominator} == 1) { $out = $self->{numerator}; } # print as 5/3 else { $out = "$self->{numerator}/$self->{denominator}"; } $out; } # these methods are simply so that in a problem, the user may access the variables without # worrying about braces, that is, use $frac->denominator instead of $frac->{denominator} sub numerator { my $self = shift; return $self->{numerator}; } sub denominator { my $self = shift; return $self->{denominator}; } ######################################################################## # Internal Methods # Least Common Multiple # Used in arithmatic methods to convert two fractions to common denominator # takes in two scalar values and returns their lcm sub lcm { my $self = shift; my $a = shift; my $b = shift; #reorder such that $a is the smaller number if ($a > $b) { my $temp = $a; $a = $b; $b = $temp; } my $lcm = 0; my $curr = $b; while($lcm == 0) { $lcm = $curr if ($curr % $a == 0); $curr += $b; } $lcm; } # Helper function for reduce # takes in two scalar values and uses the Euclidean Algorithm to return the # greatest common denominator sub gcd { my $self = shift; my $a = abs(shift); #absolute values because this will yeild the same gcd, my $b = abs(shift); #but allows use of the mod operation if ($a < $b) { my $temp = $a; $a = $b; $b = $temp; } return $a if $b == 0; my $q = int($a/$b); my $r = $a % $b; return $b if $r == 0; my $tempR = $r; while ($r != 0) { #keep track of what $r was in the last loop, as this is the value #we will want when $r is set to 0 $tempR = $r; $a = $b; $b = $r; $q = $a/$b; $r = $a % $b; } $tempR; } 1;