#!/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 = <> ); if ( \$frac01 =~ /^\S+\/(\S+)\$/ ) { print "Division by zero not allowed, starting over..." and redo unless ( \$1 ); } print "Enter second fraction: "; chomp( my \$frac02 = <> ); 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->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 equal "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