I had an assignment to write a C++ class to handle rational numbers and perform arithmetic operations on them. After completing that, I decided to write a similar version in Perl. It gave me a chance to give operator overloading a try, so I grabbed my 4th edition Camel book (pg 457).
I got everything working pretty well, but, I noticed the add and subtract functions were similar, along with the mult/divide functions. So, I wanted to trim some code lines by using the add function to handle the addition of a negative number, etc...
Also, I am welcome to comments regarding my code (or even my posting skills!). Since user input is in the form of strings: "3/4", 5/16", etc... the _make_frac internal function works, but the toggle to indicate whole numbers is not translating to my to_string, so I test the denominator, instead.
Here is the complete, "perytidy'd" code. It begins with main and the RatNum package is inline below it. This is just for fun, the real (C++) homework is done. heh
Update: I removed my variable declaration line ( habit from C++ coding, I suppose), and tried a new denominator checker.
Another Update: Ok, I've tried to see what I could do about reusing _add for the _sub function, but just could not get anything to work. I tried to simply call _add from within _sub using $x->_add($n1 + $n2), but it gave an error about not finding an overloaded string function (""). I found that overload was trying to fallback to trying to concatenate the two arguments.
I then wrote a simpler version of a package that had a single element hash, but I still could not get it to work.
I read the overload documentation about undefining the fallback option for double-string, wrote a dummy _str function and overloaded " "" ", but I'm about ready to move on from this. I was hoping I could consolidate the functions, but maybe it is better left alone. Here are the two functions that seem to say: TMTOWTDI
Thanks!
_add / _sub snippet
### Overloaded addition operator
sub _add {
my ( $x, $y ) = @_;
my ( $n_sum, $sum_str );
my ( $n1, $n2 ) = ( $x->{NUMER}, $y->{NUMER} );
my ( $d1, $d2 ) = ( $x->{DENOM}, $y->{DENOM} );
my $com_denom = ( $d1 == $d2 ) ? $d1 : $d1 * $d2;
$n_sum =
( $d1 == $d2 )
? $n1 + $n2
: do {
$n1 = $n1 * ( $com_denom / $d1 );
$n2 = $n2 * ( $com_denom / $d2 );
$n1 + $n2;
};
my $gcd = _simplify( $n_sum, $com_denom );
( $n_sum, $com_denom ) = map { $_ /= $gcd } ( $n_sum, $com_denom )
+;
$sum_str =
($com_denom)
? ( $n_sum == $com_denom )
? "1"
: "${n_sum}/${com_denom}"
: "0";
return $sum_str;
}
### Overloaded subtraction operator
sub _sub {
my ( $x, $y, ) = @_;
my ( $n_diff, $diff_str );
my ( $n1, $n2 ) = ( $x->{NUMER}, $y->{NUMER} );
my ( $d1, $d2 ) = ( $x->{DENOM}, $y->{DENOM} );
my $com_denom = ( $d1 == $d2 ) ? $d1 : $d1 * $d2;
$n_diff =
( $d1 == $d2 )
? $n1 - $n2
: do {
$n1 = $n1 * ( $com_denom / $d1 );
$n2 = $n2 * ( $com_denom / $d2 );
$n1 - $n2;
};
my $gcd = _simplify( abs($n_diff), $com_denom );
( $n_diff, $com_denom ) = map { $_ /= $gcd } ( $n_diff, $com_denom
+ );
$diff_str =
($com_denom)
? ( $n_diff == $com_denom )
? "1"
: "${n_diff}/${com_denom}"
: "0";
return $diff_str;
}
full code
#!/usr/bin/perl
use strict;
use warnings;
my $command = 0;
print "\nThis program will perform arithmetic operations on fractions.
+\n";
print "Enter fractions like this: 3/4, 15/16, etc..., or \"exit\" to e
+xit.\n";
while ( $command ne "Q" ) {
print "\nEnter first fraction: ";
chomp( my $frac01 = <> );
if ( $frac01 =~ /^\S+\/(\S+)$/ ) {
print "Division by zero not allowed, starting over..." and red
+o
unless ( $1 );
}
print "Enter second fraction: ";
chomp( my $frac02 = <> );
if ( $frac02 =~ /^\S+\/(\S+)$/ ) {
print "Division by zero not allowed, starting over..." and red
+o
unless ( $1 );
}
exit if ( $frac01 eq "exit" || $frac02 eq "exit" );
my $f1 = RatNum->new($frac01);
my $f2 = RatNum->new($frac02);
while ( $command ne "Q" ) {
print "\nChoose: (A)dd, (S)ubtract, (M)ultiply, (D)ivide\n";
print "\tTest for (E)quality, or (Q)uit\n";
print "Answer: ";
chomp( $command = <> );
if ( $command eq "A" ) {
my $total = RatNum->new( $f1 + $f2 );
print "\n"
. $f1->to_string() . " + "
. $f2->to_string() . " = "
. $total->to_string() . "\n";
}
elsif ( $command eq "S" ) {
my $total = RatNum->new( $f1 - $f2 );
print "\n"
. $f1->to_string() . " - "
. $f2->to_string() . " = "
. $total->to_string() . "\n";
}
elsif ( $command eq "M" ) {
my $total = RatNum->new( $f1 * $f2 );
print "\n"
. $f1->to_string() . " * "
. $f2->to_string() . " = "
. $total->to_string() . "\n";
}
elsif ( $command eq "D" ) {
my $total = RatNum->new( $f1 / $f2 );
print "\n"
. $f1->to_string() . " / "
. $f2->to_string() . " = "
. $total->to_string() . "\n";
}
elsif ( $command eq "E" ) {
my $is_equal = ( $f1 == $f2 ) ? q{ } : " not ";
print "\n"
. $f1->to_string() . " and "
. $f2->to_string() . " are"
. $is_equal . "equal.\n";
}
elsif ( $command eq "Q" ) {
last;
}
else {
print "Command not recognized...\n";
}
}
print "Type (Q) again to exit or another character to start over:
+";
chomp( $command = <> );
if ( $command eq "Q" ) {
print "Goodbye..\n";
exit;
}
}
exit;
### Rational Number Package ###
package RatNum;
use overload "+" => \&_add,
"-" => \&_sub,
"*" => \&_mul,
"/" => \&_div,
"==" => \&_eql;
sub new {
my $class = shift;
my $frac = {
NUMER => undef,
DENOM => undef,
WHOLE => undef,
};
bless $frac, $class;
$frac->_make_frac(@_);
return $frac;
}
### Split string and assign NUMER, DENOM, and WHOLE
sub _make_frac {
no warnings; # expecting occasional empties
my $self = shift;
my $frac_str = shift;
my ( $n, $d, $w ) = map { split /\//, $_ } ( $frac_str );
## Checks for fraction entries that can be simply "1"
( $n, $d, $w ) = ( $n, $d || 1, ( $d > 1 ) ? 0 : 1 );
( $n, $d, $w ) = (1) x 3 if ( $n == $d ); # when "2/2" should equ
+al "1"
## Checks for "-" in denominator and adjusts accordingly
$n = -$n and $d = -$d if ( ( $n > 0 ) && ( $d < 0 ) );
( $self->{NUMER}, $self->{DENOM}, $self->{WHOLE} )
= ( $n, $d, $w );
return;
}
### Stringify object to fracion
sub to_string {
my $self = shift;
my ( $n, $d, $w ) =
( $self->{NUMER}, $self->{DENOM}, $self->{WHOLE} );
return "0" unless ( $n && $d );
return $n if ( $w );
return $n . "/" . $d;
}
### Overloaded addition operator
sub _add {
my ( $x, $y ) = @_;
my ( $n_sum, $sum_str );
my ( $n1, $n2 ) = ( $x->{NUMER}, $y->{NUMER} );
my ( $d1, $d2 ) = ( $x->{DENOM}, $y->{DENOM} );
my $com_denom = ( $d1 == $d2 ) ? $d1 : $d1 * $d2;
$n_sum =
( $d1 == $d2 )
? $n1 + $n2
: do {
$n1 = $n1 * ( $com_denom / $d1 );
$n2 = $n2 * ( $com_denom / $d2 );
$n1 + $n2;
};
my $gcd = _simplify( $n_sum, $com_denom );
( $n_sum, $com_denom ) = map { $_ /= $gcd } ( $n_sum, $com_denom )
+;
$sum_str =
($com_denom)
? ( $n_sum == $com_denom )
? "1"
: "${n_sum}/${com_denom}"
: "0";
return $sum_str;
}
### Overloaded subtraction operator
sub _sub {
my ( $x, $y, ) = @_;
my ( $n_diff, $diff_str );
my ( $n1, $n2 ) = ( $x->{NUMER}, $y->{NUMER} );
my ( $d1, $d2 ) = ( $x->{DENOM}, $y->{DENOM} );
my $com_denom = ( $d1 == $d2 ) ? $d1 : $d1 * $d2;
$n_diff =
( $d1 == $d2 )
? $n1 - $n2
: do {
$n1 = $n1 * ( $com_denom / $d1 );
$n2 = $n2 * ( $com_denom / $d2 );
$n1 - $n2;
};
my $gcd = _simplify( abs($n_diff), $com_denom );
( $n_diff, $com_denom ) = map { $_ /= $gcd } ( $n_diff, $com_denom
+ );
$diff_str =
($com_denom)
? ( $n_diff == $com_denom )
? "1"
: "${n_diff}/${com_denom}"
: "0";
return $diff_str;
}
### Overloaded multiplication operator
sub _mul {
my ( $x, $y ) = @_;
my ($n_prod) = ( $x->{NUMER} * $y->{NUMER} );
my ($d_prod) = ( $x->{DENOM} * $y->{DENOM} );
my $gcd = _simplify( $n_prod, $d_prod );
( $n_prod, $d_prod ) = map { $_ /= $gcd } ( $n_prod, $d_prod );
my $prod_str = ( $n_prod == $d_prod ) ? "1" : "${n_prod}/${d_prod}
+";
return $prod_str;
}
### Overloaded division operator
sub _div {
my ( $x, $y ) = @_;
my ($n_quot) = ( $x->{NUMER} * $y->{DENOM} );
my ($d_quot) = ( $x->{DENOM} * $y->{NUMER} );
( $n_quot, $d_quot ) = ( -$n_quot, -$d_quot )
if ( $n_quot < 0 && $d_quot < 0 );
my $gcd = _simplify( $n_quot, $d_quot );
( $n_quot, $d_quot ) = map { $_ /= $gcd } ( $n_quot, $d_quot );
my $quot_str = ( $n_quot == $d_quot ) ? "1" : "${n_quot}/${d_quot}
+";
return $quot_str;
}
### Checks for object equality
sub _eql {
my ( $x, $y ) = @_;
return ( ( $x->{NUMER} == $y->{NUMER} )
&& ( $x->{DENOM} == $y->{DENOM} ) );
}
### Simplify fraction using Euclid's algorithm
sub _simplify {
use integer;
my ( $x, $y ) = @_;
while ($y) {
my $r = $x % $y;
$r += $y if $r < 0;
$x = $y;
$y = $r;
}
return $x;
}
1; # for whenever I make this a separate package file