<?xml version="1.0" encoding="windows-1252"?>
<node id="1007841" title="Overloaded operators calling similar functions..." created="2012-12-07 20:30:51" updated="2012-12-07 20:30:51">
<type id="115">
perlquestion</type>
<author id="965108">
dbuckhal</author>
<data>
<field name="doctext">
&lt;p&gt;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). &lt;/p&gt;

&lt;p&gt;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... &lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;_make_frac &lt;/code&gt; internal function works, but the toggle to indicate whole numbers is not translating to my &lt;code&gt;to_string&lt;/code&gt;, so I test the denominator, instead.&lt;/p&gt;

&lt;p&gt;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&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Update:&lt;/b&gt; I removed my variable declaration line ( habit from C++ coding, I suppose), and tried a new denominator checker.&lt;/p&gt;


&lt;p&gt;&lt;b&gt;Another Update:&lt;/b&gt; Ok, I've tried to see what I could do about reusing &lt;code&gt;_add&lt;/code&gt; for the &lt;code&gt;_sub&lt;/code&gt; function, but just could not get anything to work.  I tried to simply call &lt;code&gt;_add&lt;/code&gt; from within &lt;code&gt;_sub&lt;/code&gt; using &lt;code&gt;$x-&gt;_add($n1 + $n2)&lt;/code&gt;, but it gave an error about not finding an overloaded string function ("").  I found that &lt;code&gt;overload&lt;/code&gt; was trying to fallback to trying to concatenate the two arguments.&lt;/p&gt;
  
&lt;p&gt;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 &lt;code&gt;_str&lt;/code&gt; 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
&lt;/p&gt;
&lt;p&gt;Thanks!&lt;/p&gt;

&lt;b&gt;&lt;code&gt;_add / _sub&lt;/code&gt;&lt;/b&gt; snippet
&lt;code&gt;
### Overloaded addition operator
sub _add {
    my ( $x, $y ) = @_;
    my ( $n_sum, $sum_str );
    my ( $n1,    $n2 ) = ( $x-&gt;{NUMER}, $y-&gt;{NUMER} );
    my ( $d1,    $d2 ) = ( $x-&gt;{DENOM}, $y-&gt;{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-&gt;{NUMER}, $y-&gt;{NUMER} );
    my ( $d1, $d2 ) = ( $x-&gt;{DENOM}, $y-&gt;{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;
}
&lt;/code&gt;


&lt;b&gt;full code&lt;/b&gt;

&lt;code&gt;
#!/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 exit.\n";
while ( $command ne "Q" ) {
    print "\nEnter first fraction: ";
    chomp( my $frac01 = &lt;&gt; );
    if ( $frac01 =~ /^\S+\/(\S+)$/ ) {
        print "Division by zero not allowed, starting over..." and redo
          unless ( $1 );
    }
    print "Enter second fraction: ";
    chomp( my $frac02 = &lt;&gt; );
    if ( $frac02 =~ /^\S+\/(\S+)$/ ) {
        print "Division by zero not allowed, starting over..." and redo
          unless ( $1 );
    }
    exit if ( $frac01 eq "exit" || $frac02 eq "exit" );
    my $f1 = RatNum-&gt;new($frac01);
    my $f2 = RatNum-&gt;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 = &lt;&gt; );
        if ( $command eq "A" ) {
            my $total = RatNum-&gt;new( $f1 + $f2 );
            print "\n"
              . $f1-&gt;to_string()    . " + "
              . $f2-&gt;to_string()    . " = "
              . $total-&gt;to_string() . "\n";
        }
          elsif ( $command eq "S" ) {
            my $total = RatNum-&gt;new( $f1 - $f2 );
            print "\n"
              . $f1-&gt;to_string()    . " - "
              . $f2-&gt;to_string()    . " = "
              . $total-&gt;to_string() . "\n";
        }
        elsif ( $command eq "M" ) {
            my $total = RatNum-&gt;new( $f1 * $f2 );
            print "\n"
              . $f1-&gt;to_string()    . " * "
              . $f2-&gt;to_string()    . " = "
              . $total-&gt;to_string() . "\n";
        }
        elsif ( $command eq "D" ) {
            my $total = RatNum-&gt;new( $f1 / $f2 );
            print "\n"
              . $f1-&gt;to_string()    . " / "
              . $f2-&gt;to_string()    . " = "
              . $total-&gt;to_string() . "\n";
        }
        elsif ( $command eq "E" ) {
            my $is_equal = ( $f1 == $f2 ) ? q{ } : " not ";
            print "\n"
              . $f1-&gt;to_string() . " and "
              . $f2-&gt;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 = &lt;&gt; );
    if ( $command eq "Q" ) {
        print "Goodbye..\n";
        exit;
    }
}
exit;
### Rational Number Package ###
package RatNum;

use overload  "+"  =&gt; \&amp;_add,
              "-"  =&gt; \&amp;_sub,
              "*"  =&gt; \&amp;_mul,
              "/"  =&gt; \&amp;_div,
              "==" =&gt; \&amp;_eql;

sub new {
    my $class = shift;
    my $frac  = {
        NUMER =&gt; undef,
        DENOM =&gt; undef,
        WHOLE =&gt; undef,
    };
    bless $frac, $class;
    $frac-&gt;_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 &gt; 1 ) ? 0 : 1 );
    ( $n, $d, $w ) = (1) x 3 if ( $n == $d );  # when "2/2" should equal "1" 
##  Checks for "-" in denominator and adjusts accordingly
    $n = -$n and $d = -$d if ( ( $n &gt; 0 ) &amp;&amp; ( $d &lt; 0 ) );
    ( $self-&gt;{NUMER}, $self-&gt;{DENOM}, $self-&gt;{WHOLE} )
        = ( $n, $d, $w );
    return;
}
### Stringify object to fracion
sub to_string {
    my $self = shift;
    my ( $n, $d, $w ) =
    ( $self-&gt;{NUMER}, $self-&gt;{DENOM}, $self-&gt;{WHOLE} );
    return "0" unless ( $n &amp;&amp; $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-&gt;{NUMER}, $y-&gt;{NUMER} );
    my ( $d1,    $d2 ) = ( $x-&gt;{DENOM}, $y-&gt;{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-&gt;{NUMER}, $y-&gt;{NUMER} );
    my ( $d1, $d2 ) = ( $x-&gt;{DENOM}, $y-&gt;{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-&gt;{NUMER} * $y-&gt;{NUMER} );
    my ($d_prod) = ( $x-&gt;{DENOM} * $y-&gt;{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-&gt;{NUMER} * $y-&gt;{DENOM} );
    my ($d_quot) = ( $x-&gt;{DENOM} * $y-&gt;{NUMER} );
    ( $n_quot, $d_quot ) = ( -$n_quot, -$d_quot )
      if ( $n_quot &lt; 0 &amp;&amp; $d_quot &lt; 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-&gt;{NUMER} == $y-&gt;{NUMER} )
          &amp;&amp; ( $x-&gt;{DENOM} == $y-&gt;{DENOM} ) );
}
### Simplify fraction using Euclid's algorithm
sub _simplify {
    use integer;
    my ( $x, $y ) = @_;
    while ($y) {
        my $r = $x % $y;
        $r += $y if $r &lt; 0;
        $x = $y;
        $y = $r;
    }
    return $x;
}
1;  # for whenever I make this a separate package file
&lt;/code&gt;

</field>
</data>
</node>
