############################################################################
#
#	File:     rational.icn
#
#	Subject:  Procedures for arithmetic on rational numbers
#
#	Author:   Ralph E. Griswold
#
#	Date:     June 10, 2001
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	Contributor:  Gregg M. Townsend
#
############################################################################
#
#     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.
#
#     medrat(r1,r2) Form mediant of 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.
#
#     real2rat(v,p) Convert real to rational with precision p.
#                   The default precision is 1e-10.
#                   (Too much precision gives huge, ugly factions.)
#
#     reciprat(r)   Produce the reciprocal of rational number r.
#
#     str2rat(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 medrat(r1, r2)		#: form rational mediant
   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 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

#  real2rat(v, p) -- convert real to rational with precision p
#
#  Originally based on a calculator algorithm posted to usenet on August 19,
#  1987, by Joseph D. Rudmin, Duke University Physics Dept. (duke!dukempd!jdr)

$define MAXITER 40		# maximum number of iterations
$define PRECISION 1e-10		# default conversion precision

procedure real2rat(r, p)	#: convert to rational with precision p
   local t, d, i, j
   static x, y
   initial { x := list(MAXITER); y := list(MAXITER + 2) }

   t := abs(r)
   /p := PRECISION
   every i := 1 to MAXITER do {
      x[i] := integer(t)
      y[i + 1] := 1
      y[i + 2] := 0
      every j := i to 1 by -1 do
         y[j] := x[j] * y[j + 1] + y[j + 2]
      if abs(y[1] / real(y[2]) - r) < p then break
      d := t - integer(t)
      if d < p then break
      t := 1.0 / d
      }
   return rational(y[1], y[2], if r >= 0 then 1 else -1)

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
