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 doublestring, 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
Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
Read Where should I post X? if you're not absolutely sure you're posting in the right place.
Please read these before you post! —
Posts may use any of the Perl Monks Approved HTML tags:
 a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)

For: 

Use: 
 &   & 
 <   < 
 >   > 
 [   [ 
 ]   ] 
Link using PerlMonks shortcuts! What shortcuts can I use for linking?
See Writeup Formatting Tips and other pages linked from there for more info.