############################################################################
#
#	File:     rational.icn
#
#	Subject:  Procedures for arithmetic on rational numbers
#
#	Author:   Ralph E. Griswold
#
#	Date:     November 10, 1999
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#     These procedures perform arithmetic on rational numbers (fractions):
#
#     addrat(r1,r2) Add rational numbers r1 and r2.
#
#     divrat(r1,r2) Divide rational numbers r1 and r2.
#
#     mpyrat(r1,r2) Multiply rational numbers r1 and r2.
#
#     negrat(r)     Produce negative of rational number r.
#
#     rat2real(r)   Produce floating-point approximation of r
#
#     rat2str(r)    Convert the rational number r to its string
#                   representation.
#
#     reciprat(r)   Produce the reciprocal of rational number r.
#
#     str2rst(s)    Convert the string representation of a rational number
#                   (such as "3/2") to a rational number.
#
#     subrat(r1,r2) Subtract rational numbers r1 and r2.
#    
############################################################################
#
#  Links: numbers
#
############################################################################

link numbers

record rational(numer, denom, sign)

procedure addrat(r1, r2)		#: sum of rationals
   local denom, numer, div

   r1 := ratred(r1)
   r2 := ratred(r2)

   denom := r1.denom * r2.denom
   numer := r1.sign * r1.numer * r2.denom +
      r2.sign * r2.numer * r1.denom

   if numer = 0 then return rational (0, 1, 1)

   div := gcd(numer, denom)

   return rational(abs(numer / div), abs(denom / div), numer / abs(numer))

end

procedure divrat(r1, r2)		#: divide rationals.

   r1 := ratred(r1)
   r2 := ratred(r2)

   return mpyrat(r1, reciprat(r2))

end
 
procedure mpyrat(r1, r2)		#: multiply rationals
   local numer, denom, div

   r1 := ratred(r1)
   r2 := ratred(r2)

   numer := r1.numer * r2.numer
   denom := r1.denom * r2.denom

   div := gcd(numer, denom)

   return rational(numer / div, denom / div, r1.sign * r2.sign)

end

procedure negrat(r)		#: negative of rational

   r := ratred(r)

   return rational(r.numer, r.denom, -r.sign)

end

procedure rat2real(r)		#: floating-point approximation of rational

   r := ratred(r)

   return (real(r.numer) * r.sign) / r.denom

end
  
procedure rat2str(r)		#: convert rational to string

   r := ratred(r)

   return "(" || (r.numer * r.sign) || "/" || r.denom || ")"

end

procedure ratred(r)		#: reduce rational to lowest terms
   local div

   if r.denom = 0 then runerr(501)
   if abs(r.sign) ~= 1 then runerr(501)

   if r.numer = 0 then return rational(0, 1, 1)

   if r.numer < 0 then r.sign *:= -1
   if r.denom < 0 then r.sign *:= -1

   r.numer := abs(r.numer)
   r.denom := abs(r.denom)

   div := gcd(r.numer, r.denom)

   return rational(r.numer / div, r.denom / div, r.sign)

end

procedure reciprat(r)		#: reciprocal of rational

   r := ratred(r)

   return rational(r.denom, r.numer, r.sign)

end

procedure str2rat(s)		# convert string to rational
   local div, numer, denom, sign

   s ? {
      ="(" &
      numer := integer(tab(upto('/'))) &
      move(1) &
      denom := integer(tab(upto(')'))) &
      pos(-1)
      } | fail

   return ratred(rational(numer, denom, 1))

end

procedure subrat(r1, r2)		#: difference of rationals

   r1 := ratred(r1)
   r2 := ratred(r2)

   return addrat(r1, negrat(r2))

end