Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Overloaded operators calling similar functions...

by dbuckhal (Monk)
on Dec 08, 2012 at 01:30 UTC ( #1007841=perlquestion: print w/ replies, xml ) Need Help??
dbuckhal has asked for the wisdom of the Perl Monks concerning the following question:

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

Comment on Overloaded operators calling similar functions...
Select or Download Code
Re: Overloaded operators calling similar functions...
by Athanasius (Monsignor) on Dec 08, 2012 at 02:56 UTC

    This is a nicely-written program, well done! If the C++ version is up to this standard, you should get high marks on the assignment.

    I did notice one logical error:

    print "Division by zero not allowed, starting over..." and redo if ( $frac01 =~ /^[\s\S]?\/?0$/ );

    If the user enters 1/0, this correctly flags division by zero. But,

    • If the user enters 12/0, the regex fails and division by zero is not caught.
    • If the user enters 10, the regex matches and gives a false positive.

    How to fix this? One way would be to extract the denominator (if any) and test whether that is zero. But this is Perl, so TMTOWTDI.

    Now to style: In Perl, it’s better to declare a variable as late as possible in the code. So, looking at this line:

    my ( $f1, $f2, $frac01, $frac02, $command, $total ) = (0) x 6;

    actually only $command needs to be declared before the outer while loop. The rest can safely be declared at the point of first use. (And yes, I would declare $total separately within each of the if / elsif blocks.)

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1007841]
Approved by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (12)
As of 2014-09-16 12:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (18 votes), past polls