Ratios

Ratios as an abstract data type

One alternative to using the inexact type Real for computations involving numbers other than integers is to develop a special type for rational numbers -- numerical values that can be expressed as ratios between integers. It is often possible to arrange a computation in such a way that all or most of the steps are performed using the arithmetic of rational numbers, with no rounding error and no loss of precision during the computation.

I'll write ratios as fractions with integer numerators and denominators, and I'll adopt the convention that the denominator should always be positive and that the fraction should always be reduced to lowest terms, in the sense that the greatest common divisor of the numerator and the denominator should be 1. So, for instance, the ratio of 15 to -9 is -5/3. Equivalently, one can regard a ratio as consisting of a sign -- negative or non-negative, perhaps -- and two natural numbers, of which the second, the denominator, is non-zero.

There are two limitations to the use of ratios. One is that a number of functions that are commonly used in scientific applications -- trigonometric functions, logarithms, exponentiation to fractional powers (including square roots) -- yield irrational numbers as values even when applied to rational arguments. For this reason, they are simply not implemented as operations on ratios as an abstract data type; in practice, when applying such an operation, one converts the ratio to an approximately equal real, performs the operation on the real, and (perhaps) converts the result back to an approximately equal ratio, abandoning the attempt to obtain an exact result.

The other limitation is that in the course of a long computation on ratios, the numerators and denominators of the fractions expressing the exact results sometimes tend to get very large, much larger than MaxInt in many cases. Since we have a bignum package, this means only that that computations become very slow, but the attempt to preserve exact results can become more trouble that it is worth.

Here's a repertoire of operations on ratios:

make
Input: s, a sign, and n and d, both natural numbers.
Output: result, a ratio.
Precondition: d is not 0.
Postcondition: result is the ratio with sign s, numerator n, and denominator d.

negate
Input: negand, a ratio.
Output: result, a ratio.
Preconditions: none.
Postcondition: The sum of negand and result is 0/1.

absolute-value
Input: operand, a ratio.
Output: result, a ratio.
Preconditions: none.
Postcondition: result is the magnitude of operand, that is, its distance (in either direction) from 0/1.

negative
Input: operand, a ratio.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if operand is less than 0/1, false if it is greater or if it is 0/1.

zero
Input: operand, a ratio.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if operand is 0/1, false if it is any other integer.

positive
Input: operand, a ratio.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if operand is greater than 0/1, false if it is less or if it is 0/1.

add
Inputs: augend and addend, both ratios.
Output: sum, a ratio.
Preconditions: none.
Postcondition: sum is the sum of augend and addend.

subtract
Inputs: minuend and subtrahend, both ratios.
Output: difference, a ratio.
Preconditions: none.
Postcondition: minuend is the sum of difference and subtrahend.

multiply
Inputs: multiplicand and multiplier, both ratios.
Output: product, a ratio.
Preconditions: none.
Postcondition: product is the product of multiplicand and multiplier.

divide
Inputs: dividend and divisor, both ratios.
Output: quotient, a ratio.
Precondition: divisor is not 0/1.
Postconditions: dividend is the product of quotient and divisor.

raise
Inputs: base, a ratio, and exponent, an integer.
Output: power, a ratio.
Preconditions: Either base is not 0/1 or exponent is not negative.
Postconditions: power is the result of raising base to the power of exponent. If base is 0/1 and exponent is 0, power is 1/1.

twice
Input: operand, a ratio.
Output: result, a ratio.
Preconditions: none.
Postcondition: result is the product of operand and 2.

half
Input: operand, a ratio.
Output: result, a ratio.
Preconditions: none.
Postcondition: operand is the product of result and 2.

square
Input: operand, a ratio.
Output: result, a ratio.
Preconditions: none.
Postcondition: result is the result of raising operand to the power 2.

cube
Input: operand, a ratio.
Output: result, a ratio.
Preconditions: none.
Postcondition: result is the result of raising operand to the power 3.

reciprocal
Input: operand, a ratio.
Output: result, a ratio.
Precondition: operand is not 0/1.
Postcondition: The product of operand and result is 1/1.

equal
Inputs: left-operand and right-operand, both ratios.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if the operands are the same ratio, false if they are different ratios.

unequal
Inputs: left-operand and right-operand, both ratios.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if the operands are different ratios, false if they are the same ratio.

less
Inputs: left-operand and right-operand, both ratios.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if left-operand is less than right-operand, false if it is greater than right-operand or if both operands are the same ratio.

less-or-equal
Inputs: left-operand and right-operand, both ratios.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if left-operand is less than right-operand or if both operands are the same ratio, false if left-operand is greater than right-operand.

greater
Inputs: left-operand and right-operand, both ratios.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if left-operand is greater than right-operand, false if it is less than right-operand or if both operands are the same ratio.

greater-or-equal
Inputs: left-operand and right-operand, both ratios.
Output: result, a Boolean.
Preconditions: none.
Postcondition: result is true if left-operand is greater than right-operand or if both operands are the same ratio, false if left-operand is less than right-operand.

major
Inputs: left-operand and right-operand, both ratios.
Output: result, a ratio.
Preconditions: none.
Postcondition: result is the greater of the operands.

minor
Inputs: left-operand and right-operand, both ratios.
Output: result, a ratio.
Preconditions: none.
Postcondition: result is the lesser of the operands.

read
Input: source, a data source (e.g., a file, the keyboard, a device).
Outputs: legend, a ratio, and success, a Boolean.
Preconditions: none.
Postcondition: Either some representation of a ratio has been extracted from source, legend is that ratio, and success is true, value, or an input error of some kind has occurred and success is false.

write
Inputs: target, a data sink (e.g., a file, a window, a device), and scribend, a ratio.
Outputs: none.
Preconditions: none.
Postcondition: A representation of scribend has been appended to target.

A Ratios module in HP Pascal

Using bignums, it is straightforward to implement this data type in HP Pascal. It is convenient to add conversion functions to and from three other data types: standard Pascal integers, standard Pascal reals, and natural numbers as defined in the Naturals module.

In order to provide an opaque data type, the Ratio will actually be defined as a pointer to a record containing the sign, numerator, and denominator in the ratio. This makes it necessary to include a DeallocateRatio procedure, to recycle storage that is dynamically allocated for ratios. Since ratios will have ``pointer semantics'' with respect to assignment, I have also supplied an AssignRatio procedure that builds a copy of the ratio instead of simply copying a pointer.

{ This module defines an interface for a Ratio data type and implements it
  for HP 9000 Series 700 workstations under HP-UX 9.x, using HP Pascal.

  Programmer: John Stone, Grinnell College.
  Original version: February 13-20, 1996.
  Extensively revised: November 13, 1996.
  Reading a fraction with denominator 0 now fails: November 15, 1996.
}

$heap_dispose on$

module Ratios;

$search 'naturals.o, bidirectional-lists.o, natural-elements.o'$
import
  Naturals;

export

  type
    Signum = (Negative, Nonnegative);
    Ratio = ^RatioStructure;

  { The MakeRatio function returns the ratio of two natural numbers, with
    the specified sign (except that the sign of a zero ratio is always
    non-negative. }

  function MakeRatio (S: Signum; N, D: Natural): Ratio;

  { The NegateRatio function returns the negative, the additive inverse,
    of a given ratio. }

  function NegateRatio (Negand: Ratio): Ratio;

  { The AbsoluteValueOfRatio function returns the absolute value of a given
    ratio. } 

  function AbsoluteValueOfRatio (Operand: Ratio): Ratio;

  { The NegativeRatio function determines whether a given ratio is
    strictly Negative -- less than zero. }

  function NegativeRatio (Operand: Ratio): Boolean;

  { The ZeroRatio function determines whether a given ratio is 0/1,
    returning True if it is and False if it is not. }

  function ZeroRatio (Operand: Ratio): Boolean;

  { The PositiveRatio function determines whether a given ratio is
    strictly positive -- greater than zero. }

  function PositiveRatio (Operand: Ratio): Boolean;

  { The AddRatio function adds any two ratios and returns their sum. }

  function AddRatio (Augend, Addend: Ratio): Ratio;

  { The SubtractRatio function subtracts one ratio (the subtrahend) from
    another (the minuend) and returns their difference. } 

  function SubtractRatio (Minuend, Subtrahend: Ratio): Ratio;

  { The MultiplyRatio function multiplies one ratio (the multiplicand) by
    another (the multiplier) and returns their product. }

  function MultiplyRatio (Multiplicand, Multiplier: Ratio): Ratio;

  { The DivideRatio function divides one ratio (the dividend) by
    another (the divisor) and returns the quotient as a ratio.  It
    presupposes that the divisor is not zero. }

  function DivideRatio (Dividend, Divisor: Ratio): Ratio;

  { The RaiseRatio function raises a ratio (the base) to the power
    specified by an integer (the exponent) and returns the result as
    a ratio.  It returns 1/1 whenever the exponent is zero, even if the
    base is also zero. } 

  function RaiseRatio (Base: Ratio; Exponent: Integer): Ratio;

  { Given a ratio, the TwiceRatio function computes and returns its double
    -- that is, the ratio that is twice as large. } 

  function TwiceRatio (Operand: Ratio): Ratio;

  { Given a ratio, the HalfRatio function computes and returns its half. }

  function HalfRatio (Operand: Ratio): Ratio;

  { Given a ratio, the SquareRatio function computes and returns its
    square. }

  function SquareRatio (Operand: Ratio): Ratio;

  { Given a ratio, the CubeRatio function computes and returns its cube. }

  function CubeRatio (Operand: Ratio): Ratio;

  { Given a non-zero ratio, the ReciprocalOfRatio function computes and
    returns its reciprocal -- its multiplicative inverse. }

  function ReciprocalOfRatio (Operand: Ratio): Ratio;

  { The EqualRatios function determines whether its arguments are
    arithmetically equal -- not necessarily identical as storage
    structures, but equal in value. }

  function EqualRatios (LeftOperand, RightOperand: Ratio): Boolean;

  { The UnequalRatios function determines whether the first of its
    arguments differs in value from its second -- not whether they differ
    as storage structures, but whether their arithmetical values differ. }

  function UnequalRatios (LeftOperand, RightOperand: Ratio): Boolean;

  { The LessRatio function determines whether the first of its arguments is
    numerically less than the second. }

  function LessRatio (LeftOperand, RightOperand: Ratio): Boolean;

  { The LessOrEqualRatio function determines whether the first of its
    arguments is arithmetically greater than the second, returning False
    if it is and True if it is not.  (In other words, it returns True if
    its first argument is arithmetically less than or equal to its
    second.) } 

  function LessOrEqualRatio (LeftOperand, RightOperand: Ratio): Boolean;

  { The GreaterRatio function determines whether the first of its
    arguments is arithmetically greater than the second. }

  function GreaterRatio (LeftOperand, RightOperand: Ratio): Boolean;

  { The GreaterOrEqualRatio function determines whether the first of its
    arguments is arithmetically less than the second, returning False if it
    is and True if it is not.  (In other words, it returns True if its
    first argument is arithmetically greater than or equal to its
    second.) }

  function GreaterOrEqualRatio (LeftOperand, RightOperand: Ratio): Boolean;

  { The MajorRatio function returns the greater of its two arguments; if
    they are equal, it returns the first of the two. }

  function MajorRatio (LeftOperand, RightOperand: Ratio): Ratio;

  { The MinorRatio function returns the lesser of its two arguments; if they
    are equal, it returns the first of the two. }

  function MinorRatio (LeftOperand, RightOperand: Ratio): Ratio;

  { The ReadRatio procedure attempts to collect, from a specified text file,
    a sequence of characters consisting of zero or more whitespace
    characters, an optional sign (+ or -), one or more decimal digits, and,
    optionally, a slash and one or more further decimal digits.  If
    successful, it computes the ratio expressed by this numeral and stores
    it in the parameter Legend.  The parameter Success indicates whether
    the attempt succeeded; if Success is False, the value of Legend is
    undefined. } 

  procedure ReadRatio (var Source: Text; var Legend: Ratio;
    var Success: Boolean);

  { The WriteRatio procedure writes a base-ten fraction for a given ratio
    to a specified Text file: first, if the ratio is negative, a minus
    sign; then the numerator, with no leading zeroes or spaces (but the
    single digit '0' is written if the given ratio is 0/1); then a slash;
    and finally the denominator, again with no leading zeroes or spaces.
    It presupposes that the Text file has already been opened for output. } 

  procedure WriteRatio (var Target: Text; Scribend: Ratio);

  { The AssignRatio procedure copies a given Ratio into a variable
    location. }

  procedure AssignRatio (var Target: Ratio; Source: Ratio);

  { Given any natural number, the NaturalToRatio function constructs
    and returns a ratio of equal value. } 

  function NaturalToRatio (Operand: Natural): Ratio;

  { Given any (Pascal) integer, the PascalIntegerToRatio function
    constructs and returns a ratio of equal value. }

  function PascalIntegerToRatio (Operand: Integer): Ratio;

  { Given any (Pascal) real, the PascalRealToRatio function constructs and
    returns a ratio of equal value. } 

  function PascalRealToRatio (Operand: Real): Ratio;

  { Given any non-negative ratio, the RoundRatioToNatural function returns
    the natural number of most nearly equal value.  If the ratio is 
    exactly halfway between two natural numbers, this function returns
    whichever of them is even. }

  function RoundRatioToNatural (Operand: Ratio): Natural;

  { Given any ratio, the RoundRatioToPascalInteger function returns the
    integer of most nearly equal value.  If the ratio is exactly halfway
    between two integers, this function returns whichever of them is
    even.  This function presupposes that the value of its argument is
    neither greater than MaxInt nor less than MinInt. }

  function RoundRatioToPascalInteger (Operand: Ratio): Integer;

  { Given any ratio, the RatioToPascalReal function returns the Pascal real
    of most nearly equal value. }

  function RatioToPascalReal (Operand: Ratio): Real;

  { The DeallocateRatio procedure frees all of the storage allocated for
    a given ratio and changes the value of its argument to nil.  It
    presupposes that a ratio has been stored in R and not previously
    deallocated. } 

  procedure DeallocateRatio (var R: Ratio);

implement

  import
    StdErr;

    { The following constants are more or less arbitrary integers
      signifying various kinds of exceptions that can occur within this
      module. }

  const
    FirstExceptionCode = 1;
    UninitializedRatioException = 1;
    MakeRatioException = 2;
    InvalidRatioException = 3;
    DivideRatioException = 4;
    RaiseRatioException = 5;
    ReciprocalOfRatioException = 6;
    RatioToNaturalException = 7;
    RatioToPascalIntegerException = 8;
    ExceptionException = 9;
    LastExceptionCode = ExceptionException;

    { The structure used to represent a ratio consists of a sign, a
      numerator, and a denominator.  The numerator and the denominator
      are natural numbers, and it is an invariant of this module that
      in every ratio that is released, the numerator and denominator
      are relatively prime.  Also, the denominator is never zero. }

    Debug = True;
      { True during debugging, False (for greater speed) when the
        module is released }

  type
    RatioStructure = record
                        Sign: Signum;
                        Numerator: Natural;
                        Denominator: Natural
                      end;

  { The RatioExceptionHandler procedure is invoked whenever one of the
    preconditions for the successful execution of a procedure is found to
    be false.  It prints out an appropriate explanation of the exception
    just before the program is halted. } 

  procedure RatioExceptionHandler (ExceptionCode: Integer);
  begin
    if (ExceptionCode < FirstExceptionCode) or
                                (LastExceptionCode < ExceptionCode) then
      ExceptionCode := ExceptionException;
    Write (StdErr, 'Exception #', ExceptionCode : 1,
           ' in module Ratios: ');
    case ExceptionCode of
      UninitializedRatioException:
        WriteLn (StdErr, 'uninitialized ratio provided as argument');
      MakeRatioException:
        WriteLn (StdErr, 'attempt to construct a ratio with denominator ',
                 'zero in function MakeRatio');
      InvalidRatioException:
        WriteLn (StdErr, 'attempt to return an incorrectly constructed ',
                 'ratio');
      DivideRatioException:
        WriteLn (StdErr, 'division by zero attempted in function ',
                 'DivideRatio');
      RaiseRatioException:
        WriteLn (StdErr, 'attempt to raise zero to a negative power in ',
                 'function RaiseRatio');
      ReciprocalOfRatioException:
        WriteLn (StdErr, 'zero argument to function ReciprocalOfRatio');
      RatioToNaturalException:
        WriteLn (StdErr, 'Negative argument to function ',
                 'RoundRatioToNatural');
      RatioToPascalIntegerException:
        WriteLn (StdErr, 'argument out of range in function ',
                 'RoundRatioToPascalInteger');
      ExceptionException:
        WriteLn (StdErr, 'argument out of range in procedure ',
                 'RatioExceptionHandler.')
    end
  end;

  { The GCD function finds and returns the greatest common divisor of any
    two natural numbers, using Euclid's algorithm.  If either of its
    arguments is zero, the function returns a copy of the other argument. }

  function GCD (LeftOperand, RightOperand: Natural): Natural;
  var
    Primus, Secundus: Natural;
      { initially copies of LeftOperand and RightOperand, but decreasing
        towards the greatest common divisor and zero, respectively }
    Rest: Natural;
      { the remainder after a trial division }
  begin
    AssignNatural (Primus, LeftOperand);
    AssignNatural (Secundus, RightOperand);
    while not ZeroNatural (Secundus) do begin
      Rest := RemainderNatural (Primus, Secundus);
      DeallocateNatural (Primus);
      Primus := Secundus;
      Secundus := Rest
    end;
    DeallocateNatural (Secundus);
    GCD := Primus
  end;

  { The ValidRatio function determines whether its argument is valid as
    a ratio.  (A valid ratio has a non-zero denominator and its numerator
    and denominator are relatively prime.) }

  function ValidRatio (Operand: Ratio): Boolean;
  var
    Common: Natural;
      { the greatest common divisor of the ratio's numerator and
        denominator }
    One: Natural;
      { the natural number 1 }
  begin
    if Operand = nil then
      ValidRatio := False
    else if ZeroNatural (Operand^.Denominator) then
      ValidRatio := False
    else begin
      Common := GCD (Operand^.Numerator, Operand^.Denominator);
      One := PascalIntegerToNatural (1);
      ValidRatio := EqualNaturals (Common, One);
      DeallocateNatural (Common);
      DeallocateNatural (One)
    end
  end;

  { The BuildRatio function allocates storage for a ratio and initializes
    its fields with the specified values, allocating fresh storage for
    the numerator and denominator if the value of the parameter ut
    (``use or throw out'') is False, but using the given storage if it is
    True.  This function presupposes that D is non-zero and that N and D
    are relatively prime. }

  function BuildRatio (S: Signum; N: Natural; D: Natural; UT: Boolean):
    Ratio;  
  var
    Result: Ratio;
      { a pointer to the newly allocated record }
  begin
    New (Result);
    if ZeroNatural (N) then
      Result^.Sign := Nonnegative
    else
      Result^.Sign := S;
    if UT then begin
      Result^.Numerator := N;
      Result^.Denominator := D
    end
    else begin
      AssignNatural (Result^.Numerator, N);
      AssignNatural (Result^.Denominator, D)
    end;
    BuildRatio := Result
  end;

  { The BuildAndReduce function constructs and returns a ratio having
    the specified sign and an absolute value is equal to the quotient of N 
    and D.  Unlike BuildRatio, it does not assume that N and D are
    relatively prime, though it does presuppose that D is not zero. }

  function BuildAndReduce (S: Signum; N: Natural; D: Natural;
    UT: Boolean): Ratio; 
  var
    Common: Natural;
      { the greatest common divisor of N and D }
    One: Natural;
      { 1, as a Natural number }
  begin
    Common := GCD (N, D);
    One := PascalIntegerToNatural (1);
    if EqualNaturals (Common, One) then
      BuildAndReduce := BuildRatio (S, N, D, UT)
    else begin
      BuildAndReduce := BuildRatio (S, QuotientNatural (N, Common),
                                       QuotientNatural (D, Common), True);
      if UT then begin
        DeallocateNatural (N);
        DeallocateNatural (D)
      end
    end;
    DeallocateNatural (Common);
    DeallocateNatural (One)
  end;

  { The Opposite function returns the sign opposite to any given sign. }

  function Opposite (S: Signum): Signum;
  begin
    case S of
    Negative:
      Opposite := Nonnegative;
    Nonnegative:
      Opposite := Negative
    end
  end;

  function MakeRatio (S: Signum; N, D: Natural): Ratio;
  var
    Result: Ratio;
  begin
    Assert (not ZeroNatural (D), MakeRatioException, RatioExceptionHandler);
    Result := BuildAndReduce (S, N, D, False);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    MakeRatio := Result
  end;

  function NegateRatio (Negand: Ratio): Ratio;
  var
    Result: Ratio;
  begin
    Assert (Negand <> nil, UninitializedRatioException,
            RatioExceptionHandler);
    if (Negand^.Sign = Negative) or ZeroNatural (Negand^.Numerator) then
      Result := BuildRatio (Nonnegative, Negand^.Numerator,
                            Negand^.Denominator, False)
    else
      Result := BuildRatio (Negative, Negand^.Numerator,
                            Negand^.Denominator, False);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    NegateRatio := Result
  end;

  function AbsoluteValueOfRatio (Operand: Ratio): Ratio;
  var
    Result: Ratio;
  begin
    Assert (Operand <> nil, UninitializedRatioException,
            RatioExceptionHandler);
    Result := BuildRatio (Nonnegative, Operand^.Numerator,
                          Operand^.Denominator, False);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    AbsoluteValueOfRatio := Result
  end;

  function NegativeRatio (Operand: Ratio): Boolean;
  begin
    Assert (Operand <> nil, UninitializedRatioException,
            RatioExceptionHandler);
    NegativeRatio := (Operand^.Sign = Negative)
  end;

  function ZeroRatio (Operand: Ratio): Boolean;
  begin
    Assert (Operand <> nil, UninitializedRatioException,
            RatioExceptionHandler);
    ZeroRatio := ZeroNatural (Operand^.Numerator)
  end;

  function PositiveRatio (Operand: Ratio): Boolean;
  begin
    Assert (Operand <> nil, UninitializedRatioException,
            RatioExceptionHandler);
    PositiveRatio := (Operand^.Sign = Nonnegative) and
                      not ZeroNatural (Operand^.Numerator)
  end;

  function AddRatio (Augend, Addend: Ratio): Ratio;
  var
    AD, BC, BD: Natural;
      { a/b + c/d = (ad + bc) / bd }
    Result: Ratio;
  begin
    Assert ((Augend <> nil) and (Addend <> nil),
            UninitializedRatioException, 
            RatioExceptionHandler);
    AD := MultiplyNatural (Augend^.Numerator, Addend^.Denominator);
    BC := MultiplyNatural (Augend^.Denominator, Addend^.Numerator);
    BD := MultiplyNatural (Augend^.Denominator, Addend^.Denominator);
    if Augend^.Sign = Addend^.Sign then
      Result := BuildAndReduce (Augend^.Sign, AddNatural (AD, BC), BD,
                                True)
    else if LessNatural (AD, BC) then
      Result := BuildAndReduce (Addend^.Sign, SubtractNatural (BC, AD),
                                BD, True)
    else if LessNatural (BC, AD) then
      Result := BuildAndReduce (Augend^.Sign, SubtractNatural (AD, BC),
                                BD, True)
    else begin
      DeallocateNatural (BD);
      Result := BuildRatio (Nonnegative, PascalIntegerToNatural (0),
                            PascalIntegerToNatural (1), True)
    end;
    DeallocateNatural (AD);
    DeallocateNatural (BC);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    AddRatio := Result
  end;

  function SubtractRatio (Minuend, Subtrahend: Ratio): Ratio;
  var
    AD, BC, BD: Natural;
      { a/b - c/d = (ad - bc) / bd }
    Result: Ratio;
  begin
    Assert ((Minuend <> nil) and (Subtrahend <> nil),
            UninitializedRatioException, 
            RatioExceptionHandler);
    AD := MultiplyNatural (Minuend^.Numerator, Subtrahend^.Denominator);
    BC := MultiplyNatural (Minuend^.Denominator, Subtrahend^.Numerator);
    BD := MultiplyNatural (Minuend^.Denominator, Subtrahend^.Denominator);
    if Minuend^.Sign <> Subtrahend^.Sign then
      Result := BuildAndReduce (Minuend^.Sign, AddNatural (AD, BC),
                                BD, True)
    else if LessNatural (AD, BC) then
      Result := BuildAndReduce (Opposite (Minuend^.Sign),
                                SubtractNatural (BC, AD), BD, True)
    else if LessNatural (BC, AD) then
      Result := BuildAndReduce (Minuend^.Sign,
                                SubtractNatural (AD, BC), BD, True)
    else begin
      DeallocateNatural (BD);
      Result := BuildRatio (Nonnegative, PascalIntegerToNatural (0), 
                            PascalIntegerToNatural (1), True)
    end;
    DeallocateNatural (AD);
    DeallocateNatural (BC);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    SubtractRatio := Result
  end;

  function MultiplyRatio (Multiplicand, Multiplier: Ratio): Ratio;
  var
    Result: Ratio;
  begin
    Assert ((Multiplicand <> nil) and (Multiplier <> nil),
            UninitializedRatioException, 
            RatioExceptionHandler);
    if (Multiplicand^.Sign = Multiplier^.Sign) or
       ZeroNatural (Multiplicand^.Numerator) or
       ZeroNatural (Multiplier^.Numerator) then
      Result :=
        BuildAndReduce(Nonnegative,
                       MultiplyNatural (Multiplicand^.Numerator,
                                        Multiplier^.Numerator),
                       MultiplyNatural (Multiplicand^.Denominator,
                                        Multiplier^.Denominator),
                       True)
    else
      Result :=
        BuildAndReduce(Negative,
                       MultiplyNatural (Multiplicand^.Numerator,
                                        Multiplier^.Numerator),
                       MultiplyNatural (Multiplicand^.Denominator,
                                        Multiplier^.Denominator),
                       True);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    MultiplyRatio := Result
  end;

  function DivideRatio (Dividend, Divisor: Ratio): Ratio;
  var
    Result: Ratio;
  begin
    Assert ((Dividend <> nil) and (Divisor <> nil),
            UninitializedRatioException, 
            RatioExceptionHandler);
    Assert (not ZeroNatural (Divisor^.Numerator), DivideRatioException,
            RatioExceptionHandler);
    if (Dividend^.Sign = Divisor^.Sign) or
       ZeroNatural (Dividend^.Numerator) then
      Result :=
        BuildAndReduce(Nonnegative,
                       MultiplyNatural (Dividend^.Numerator,
                                        Divisor^.Denominator),
                       MultiplyNatural (Dividend^.Denominator,
                                        Divisor^.Numerator),
                       True)
    else
      Result :=
        BuildAndReduce(Negative,
                       MultiplyNatural (Dividend^.Numerator,
                                        Divisor^.Denominator),
                       MultiplyNatural (Dividend ^.Denominator,
                                        Divisor^.Numerator),
                       True);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    DivideRatio := Result
  end;

  function RaiseRatio (Base: Ratio; Exponent: Integer): Ratio;
  var
    S: Signum;
      { the sign of the result }
    Temporary: Natural;
      { temporary storage for an intermediate value in the construction
        of NaturalExponent }
    NaturalExponent: Natural;
      { the absolute value of Exponent, as a natural number }
    Result: Ratio;
  begin
    Assert (Base <> nil, UninitializedRatioException, 
            RatioExceptionHandler);
    Assert (not ZeroRatio (Base) or (0 <= Exponent), RaiseRatioException,
            RatioExceptionHandler);
    if Odd (Exponent) then
      S := Base^.Sign
    else
      S := Nonnegative;
    if Exponent = MinInt then begin
      Temporary := PascalIntegerToNatural (MaxInt);
      NaturalExponent := SuccessorOfNatural (Temporary);
      DeallocateNatural (Temporary)
    end
    else
      NaturalExponent := PascalIntegerToNatural (Abs (Exponent));
    if Exponent < 0 then
      Result :=
        BuildRatio (S, RaiseNatural (Base^.Denominator, NaturalExponent),
                    RaiseNatural (Base^.Numerator, NaturalExponent),
                    True)
    else
      Result :=
        BuildRatio (S, RaiseNatural (Base^.Numerator, NaturalExponent),
                    RaiseNatural (Base^.Denominator, NaturalExponent),
                    True);
    DeallocateNatural (NaturalExponent);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    RaiseRatio := Result
  end;

  function TwiceRatio (Operand: Ratio): Ratio;
  var
    Result: Ratio;
      { the newly allocated ratio, twice as large as the given one }
    Two: Natural;
      { the natural number 2 }
  begin
    Assert (Operand <> nil, UninitializedRatioException, 
            RatioExceptionHandler);
    New (Result);
    Result^.Sign := Operand^.Sign;
    if EvenNatural (Operand^.Denominator) then begin
      AssignNatural (Result^.Numerator, Operand^.Numerator);
      Two := PascalIntegerToNatural (2);
      Result^.Denominator := QuotientNatural (Operand^.Denominator, Two);
      DeallocateNatural (Two)
    end
    else begin
      Result^.Numerator := TwiceNatural (Operand^.Numerator);
      AssignNatural (Result^.Denominator, Operand^.Denominator)
    end;
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    TwiceRatio := Result
  end;

  function HalfRatio (Operand: Ratio): Ratio;
  var
    Result: Ratio;
      { the newly allocated ratio, half as large as the given one }
    Two: Natural;
      { the natural number 2 }
  begin
    Assert (Operand <> nil, UninitializedRatioException, 
            RatioExceptionHandler);
    New (Result);
    Result^.Sign := Operand^.Sign;
    if EvenNatural (Operand^.Numerator) then begin
      Two := PascalIntegerToNatural (2);
      Result^.Numerator := QuotientNatural (Operand^.Numerator, Two);
      DeallocateNatural (Two);
      AssignNatural (Result^.Denominator, Operand^.Denominator)
    end
    else begin
      AssignNatural (Result^.Numerator, Operand^.Numerator);
      Result^.Denominator := TwiceNatural (Operand^.Denominator)
    end;
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    HalfRatio := Result
  end;

  function SquareRatio (Operand: Ratio): Ratio;
  var
    Result: Ratio;
  begin
    Assert (Operand <> nil, UninitializedRatioException, 
            RatioExceptionHandler);
    Result := BuildRatio (Nonnegative,
                          SquareNatural (Operand^.Numerator),
                          SquareNatural (Operand^.Denominator), True);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    SquareRatio := Result
  end;

  function CubeRatio (Operand: Ratio): Ratio;
  var
    Result: Ratio;
  begin
    Assert (Operand <> nil, UninitializedRatioException, 
            RatioExceptionHandler);
    Result := BuildRatio (Operand^.Sign,
                          CubeNatural (Operand^.Numerator),
                          CubeNatural (Operand^.Denominator), True);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    CubeRatio := Result
  end;

  function ReciprocalOfRatio (Operand: Ratio): Ratio;
  var
    Result: Ratio;
  begin
    Assert (Operand <> nil, UninitializedRatioException, 
            RatioExceptionHandler);
    Assert (not ZeroNatural (Operand^.Numerator),
            ReciprocalOfRatioException,
            RatioExceptionHandler);
    Result := BuildRatio (Operand^.Sign, Operand^.Denominator,
                          Operand^.Numerator, False);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    ReciprocalOfRatio := Result
  end;

  function EqualRatios (LeftOperand, RightOperand: Ratio): Boolean;
  var
    AD, BC: Natural;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if (LeftOperand^.Sign = RightOperand^.Sign) then begin
      AD := MultiplyNatural (LeftOperand^.Numerator,
                             RightOperand^.Denominator);
      BC := MultiplyNatural (LeftOperand^.Denominator,
                             RightOperand^.Numerator);
      EqualRatios := EqualNaturals (AD, BC);
      DeallocateNatural (AD);
      DeallocateNatural (BC)
    end
    else
      EqualRatios := False
  end;

  function UnequalRatios (LeftOperand, RightOperand: Ratio): Boolean;
  var
    AD, BC: Natural;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if (LeftOperand^.Sign = RightOperand^.Sign) then begin
      AD := MultiplyNatural (LeftOperand^.Numerator,
                             RightOperand^.Denominator);
      BC := MultiplyNatural (LeftOperand^.Denominator,
                             RightOperand^.Numerator);
      UnequalRatios := UnequalNaturals (AD, BC);
      DeallocateNatural (AD);
      DeallocateNatural (BC)
    end
    else
      UnequalRatios := True
  end;

  function LessRatio (LeftOperand, RightOperand: Ratio): Boolean;
  var
    AD, BC: Natural;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if LeftOperand^.Sign = RightOperand^.Sign then begin
      AD := MultiplyNatural (LeftOperand^.Numerator,
                             RightOperand^.Denominator);
      BC := MultiplyNatural (LeftOperand^.Denominator,
                             RightOperand^.Numerator);
      case LeftOperand^.Sign of
      Negative:
        LessRatio := LessNatural (BC, AD);
      Nonnegative:
        LessRatio := LessNatural (AD, BC);
      end;
      DeallocateNatural (AD);
      DeallocateNatural (BC)
    end
    else
      LessRatio := (LeftOperand^.Sign = Negative)
  end;

  function LessOrEqualRatio (LeftOperand, RightOperand: Ratio): Boolean;
  var
    AD, BC: Natural;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if LeftOperand^.Sign = RightOperand^.Sign then begin
      AD := MultiplyNatural (LeftOperand^.Numerator,
                             RightOperand^.Denominator);
      BC := MultiplyNatural (LeftOperand^.Denominator,
                             RightOperand^.Numerator);
      case LeftOperand^.Sign of
      Negative:
        LessOrEqualRatio := LessOrEqualNatural (BC, AD);
      Nonnegative:
        LessOrEqualRatio := LessOrEqualNatural (AD, BC);
      end;
      DeallocateNatural (AD);
      DeallocateNatural (BC)
    end
    else
      LessOrEqualRatio := (LeftOperand^.Sign = Negative)
  end;

  function GreaterRatio (LeftOperand, RightOperand: Ratio): Boolean;
  var
    AD, BC: Natural;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if LeftOperand^.Sign = RightOperand^.Sign then begin
      AD := MultiplyNatural (LeftOperand^.Numerator,
                             RightOperand^.Denominator);
      BC := MultiplyNatural (LeftOperand^.Denominator,
                             RightOperand^.Numerator);
      case LeftOperand^.Sign of
      Negative:
        GreaterRatio := GreaterNatural (BC, AD);
      Nonnegative:
        GreaterRatio := GreaterNatural (AD, BC);
      end;
      DeallocateNatural (AD);
      DeallocateNatural (BC)
    end
    else
      GreaterRatio := (LeftOperand^.Sign = Nonnegative)
  end;

  function GreaterOrEqualRatio (LeftOperand, RightOperand: Ratio): Boolean;
  var
    AD, BC: Natural;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if LeftOperand^.Sign = RightOperand^.Sign then begin
      AD := MultiplyNatural (LeftOperand^.Numerator,
                             RightOperand^.Denominator);
      BC := MultiplyNatural (LeftOperand^.Denominator,
                             RightOperand^.Numerator);
      case LeftOperand^.Sign of
      Negative:
        GreaterOrEqualRatio := GreaterOrEqualNatural (BC, AD);
      Nonnegative:
        GreaterOrEqualRatio := GreaterOrEqualNatural (AD, BC);
      end;
      DeallocateNatural (AD);
      DeallocateNatural (BC)
    end
    else
      GreaterOrEqualRatio := (LeftOperand^.Sign = Nonnegative)
  end;

  function MajorRatio (LeftOperand, RightOperand: Ratio): Ratio;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if GreaterOrEqualRatio (LeftOperand, RightOperand) then
      MajorRatio := LeftOperand
    else
      MajorRatio := RightOperand
  end;

  function MinorRatio (LeftOperand, RightOperand: Ratio): Ratio;
  begin
    Assert ((LeftOperand <> nil) and (RightOperand <> nil),
            UninitializedRatioException,  
            RatioExceptionHandler);
    if LessOrEqualRatio (LeftOperand, RightOperand) then
      MinorRatio := LeftOperand
    else
      MinorRatio := RightOperand
  end;

  procedure ReadRatio (var Source: Text; var Legend: Ratio;
    var Success: Boolean);
  label
    99;
      { early exit if a syntax error is detected }
  var
    S: Signum;
      { the sign of the ratio, as recovered from the source file }
    N, D: Natural;
      { the numerator and denominator of the Ratio, as recovered from
        the source file }
    Slash: Boolean;
      { indicates whether there is a slash after the numerator,
        presumably followed by an explicit denominator }

  { The SkipWhiteSpace procedure advances through a text file until
    a non-whitespace character (or the end of the file) is encountered. }

    procedure SkipWhiteSpace (var Source: Text);
    const
      Space = ' ';
    var
      Finished: Boolean;
        { indicates whether it is necessary to keep looking for white
          space to skip }
    begin
      Finished := False;
      while not Finished do
        if EOF (Source) then
          Finished := True
        else if (Source^ <= Space) or (Source^ = Chr (127)) then
          Get (Source)
        else
          Finished := True
    end;

    function Numeric (Ch: Char): Boolean;
    begin
      Numeric := ('0' <= Ch) and (Ch <= '9')
    end;

  begin { procedure ReadRatio }
    SkipWhiteSpace (Source);
    if EOF (Source) then begin
      Success := False;
      goto 99
    end;

    { Recover the sign of the ratio. }

    if Source^ = '-' then begin
      S := Negative;
      Get (Source);
      if EOF (Source) then begin
        Success := False;
        goto 99
      end
    end
    else if Source^ = '+' then begin
      S := Nonnegative;
      Get (Source);
      if EOF (Source) then begin
        Success := False;
        goto 99
      end
    end
    else
      S := Nonnegative;

    { Read in the numerator. }

    ReadNatural (Source, N, Success);
    if not Success then
      goto 99;

    { Deal with the slash, if it is present. }

    if EOF (Source) then
      Slash := False
    else
      Slash := (Source^ = '/');
    if Slash then begin

      { Read in the denominator. }

      Get (Source);
      ReadNatural (Source, D, Success);
      if ZeroNatural (D) then begin
        Success := False;
        DeallocateNatural (D)
      end;
      if not Success then begin
        DeallocateNatural (N);
        goto 99
      end

    end
    else
      D := PascalIntegerToNatural (1);

    if ZeroNatural (N) then
      S := Nonnegative;
    Legend := BuildAndReduce (S, N, D, True);
    if Debug then
      Assert (ValidRatio (Legend), InvalidRatioException,
              RatioExceptionHandler);
  99:
  end;

  procedure WriteRatio (var Target: Text; Scribend: Ratio);
  begin
    Assert (Scribend <> nil, UninitializedRatioException,  
            RatioExceptionHandler);
    if Scribend^.Sign = Negative then
      Write (Target, '-');
    WriteNatural (Target, Scribend^.Numerator);
    Write (Target, '/');
    WriteNatural (Target, Scribend^.Denominator)
  end;

  procedure AssignRatio (var Target: Ratio; Source: Ratio);
  begin
    Assert (Source <> nil, UninitializedRatioException,  
            RatioExceptionHandler);
    New (Target);
    Target^.Sign := Source^.Sign;
    AssignNatural (Target^.Numerator, Source^.Numerator);
    AssignNatural (Target^.Denominator, Source^.Denominator);
    if Debug then
      Assert (ValidRatio (Target), InvalidRatioException,
              RatioExceptionHandler)
  end;

  function NaturalToRatio (Operand: Natural): Ratio;
  var
    Result: Ratio;
  begin
    New (Result);
    Result^.Sign := Nonnegative;
    AssignNatural (Result^.Numerator, Operand);
    Result^.Denominator := PascalIntegerToNatural (1);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    NaturalToRatio := Result
  end;

  function PascalIntegerToRatio (Operand: Integer): Ratio;
  var
    One: Natural;
      { the natural number 1 }
    S: Signum;
      { the sign of Operand, and hence of the ratio }
    MaxIntAsNatural: Natural;
      { a natural number equal in value to MaxInt }
    Numer: Natural;
      { the numerator of the ratio }
    Result: Ratio;
  begin
    One := PascalIntegerToNatural (1);
    if Operand = MinInt then begin
      S := Negative;
      MaxIntAsNatural := PascalIntegerToNatural (MaxInt);
      Numer := AddNatural (MaxIntAsNatural, One);
      DeallocateNatural (MaxIntAsNatural)
    end
    else begin
      if Operand < 0 then
        S := Negative
      else
        S := Nonnegative;
      Numer := PascalIntegerToNatural (Abs (Operand))
    end;
    Result := BuildRatio (S, Numer, One, True);
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    PascalIntegerToRatio := Result
  end;

  function PascalRealToRatio (Operand: Real): Ratio;
  var
    S: Signum;
      { the sign of the resulting ratio }
    Exponent: Integer;
      { the exponent of a power of two that must be multiplied into the
        ratio to make it equal in value to the original operand }
    Mantissa: Natural;
      { the value of one or more leading bits of the mantissa; ultimately,
        the value of the bits of the mantissa up to and including the
        rightmost one-bit }
    Temporary: Natural;
      { temporary storage for a pointer to the next value of Mantissa }
    Two: Natural;
      { the natural number 2 }
    NaturalExponent: Natural;
      { a natural number equal in value to the absolute value of Exponent }
    PowerOfTwo: Natural;
      { two to the power of NaturalExponent }
    Result: Ratio;
  begin
    if Operand = 0.0 then
      Result := BuildRatio (Nonnegative, PascalIntegerToNatural (0),
                            PascalIntegerToNatural (1), True)
    else begin
      if Operand < 0.0 then begin
        S := Negative;
        Operand := -Operand
      end
      else begin
        S := Nonnegative
      end;

      { Operand is now greater than zero.  Now we normalize it, dividing
        or multiplying it repeatedly by 2 until it is in the range from
        1.0 (inclusive) to 2.0 (exclusive). }

      Exponent := 0;
      while 2.0 <= Operand do begin
        Operand := Operand / 2;
        Exponent := Exponent + 1
      end;
      while Operand < 1.0 do begin
        Operand := Operand * 2;
        Exponent := Exponent - 1
      end;

      { Next, we recover the mantissa one bit at a time, starting with
        the hidden bit and continuing until the operand has been reduced
        to 0.0. }

      Mantissa := PascalIntegerToNatural (1);
      Operand := Operand - 1.0;
      while Operand <> 0.0 do begin
        Operand := Operand * 2;
        Temporary := TwiceNatural (Mantissa);
        DeallocateNatural (Mantissa);
        if 1.0 <= Operand then begin
          Operand := Operand - 1.0;
          Mantissa := SuccessorOfNatural (Temporary);
          DeallocateNatural (Temporary)
        end
        else
          Mantissa := Temporary;
        Exponent := Exponent - 1
      end;

      { If the exponent is still non-negative, the ratio has the mantissa
        times 2^exponent as its numerator and 1 as its denominator;
        if the exponent is negative, the ratio has the mantissa as its
        numerator and 2^(-exponent) as its denominator. }

      Two := PascalIntegerToNatural (2);
      if 0 <= Exponent then begin
        NaturalExponent := PascalIntegerToNatural (Exponent);
        PowerOfTwo := RaiseNatural (Two, NaturalExponent);
        DeallocateNatural (NaturalExponent);
        Result := BuildRatio (S, MultiplyNatural (Mantissa, PowerOfTwo),
                              PascalIntegerToNatural (1), True);
        DeallocateNatural (PowerOfTwo);
        DeallocateNatural (Mantissa)
      end
      else begin
        NaturalExponent := PascalIntegerToNatural (-Exponent);
        Result := BuildRatio (S, Mantissa,
                              RaiseNatural (Two, NaturalExponent), True);
        DeallocateNatural (NaturalExponent)
      end;
      DeallocateNatural (Two)
    end;
    if Debug then
      Assert (ValidRatio (Result), InvalidRatioException,
              RatioExceptionHandler);
    PascalRealToRatio := Result
  end;

  function RoundRatioToNatural (Operand: Ratio): Natural;
  var
    Quot, Rem: Natural;
      { the quotient and remainder, respectively, when the numerator of the
        ratio is divided by its denominator }
    TwiceRem: Natural;
      { a natural number twice as large as Rem }
  begin
    Assert (Operand <> nil, UninitializedRatioException,  
            RatioExceptionHandler);
    Assert (Operand^.Sign = Nonnegative, RatioToNaturalException,
            RatioExceptionHandler);
    DivideNatural (Operand^.Numerator, Operand^.Denominator, Quot, Rem);
    TwiceRem := TwiceNatural (Rem);
    DeallocateNatural (Rem);
    if LessNatural (Operand^.Denominator, TwiceRem) or
                (EqualNaturals (Operand^.Denominator, TwiceRem) and
                 OddNatural (Quot)) then begin
      RoundRatioToNatural := SuccessorOfNatural (Quot);
      DeallocateNatural (Quot)
    end
    else
      RoundRatioToNatural := Quot;
    DeallocateNatural (TwiceRem)
  end;

  function RoundRatioToPascalInteger (Operand: Ratio): Integer;
  const
    ValueBitsInInteger = 31;
      { the number of bits in the representation of an integer, not
        counting the sign bit }
  var
    OriginalSign: Signum;
      { the sign of the ratio, and hence of the integer }
    Rounded: Natural;
      { the natural number nearest to the absolute value of the ratio }
    BitCounter: Integer;
      { counts off the value bits in the representation of an integer }
    Two: Natural;
      { the natural number 2 }
    PowerOfTwo: Integer;
      { a power of two, to be added to the absolute value of the integer
        to be returned }
    Temporary: Natural;
      { temporary storage for a pointer to the next value of Rounded }
    Result: Integer;
      { the absolute value of the integer to be returned, computed one bit
        at a time }
  begin
    Assert (Operand <> nil, UninitializedRatioException,  
            RatioExceptionHandler);
    OriginalSign := Operand^.Sign;
    Operand^.Sign := Nonnegative;
    Rounded := RoundRatioToNatural (Operand);
    Operand^.Sign := OriginalSign;
    BitCounter := 0;
    Result := 0;
    Two := PascalIntegerToNatural (2);
    PowerOfTwo := 1;
    while (BitCounter < ValueBitsInInteger) and
                                        not ZeroNatural (Rounded) do begin
      if OddNatural (Rounded) then
         Result := Result + PowerOfTwo;
      Temporary := QuotientNatural (Rounded, Two);
      DeallocateNatural (Rounded);
      Rounded := Temporary;
      if BitCounter <> ValueBitsInInteger - 1 then
        PowerOfTwo := PowerOfTwo * 2;
      BitCounter := BitCounter + 1
    end;
    if (Result = 0) and (OriginalSign = Negative) then begin
      Temporary := PredecessorOfNatural (Rounded);
      DeallocateNatural (Rounded);
      Rounded := Temporary;
      Assert (ZeroNatural (Rounded), RatioToPascalIntegerException,
              RatioExceptionHandler);
      RoundRatioToPascalInteger := MinInt
    end
    else begin
      Assert (ZeroNatural (Rounded), RatioToPascalIntegerException,
              RatioExceptionHandler);
      case OriginalSign of
      Negative:
        RoundRatioToPascalInteger := -Result;
      Nonnegative:
        RoundRatioToPascalInteger := Result
      end
    end;
    DeallocateNatural (Rounded);
    DeallocateNatural (Two)
  end;

  function RatioToPascalReal (Operand: Ratio): Real;
  const
    MantissaBits = 23;
      { the number of non-hidden bits in the mantissa of a Real (here an
        IEEE single-precision real }
  var
    Exponent: Integer;
      { the exponent of a power of two, by which the mantissa must be
        multiplied }
    Top, Bottom: Natural;
      { originally the numerator and denominator of the ratio, subsequently
        shifted to normalize the fraction }
    Temporary: Natural;
      { temporary storage for the next value of some variable }
    Mantissa: Real;
      { the value of the mantissa of the real number, constructed bit by
        bit }
    PowerOfTwo: Real;
      { a power of two, to be added to the mantissa if the next bit of
        that mantissa should be turned on }
    BitNumber: Integer;
      { counts off the bits of the mantissa as they are generated }
    Result: Real;
      { initially, the completed mantissa of the real number to be
        returned; subsequently scaled to reflect the exponent }
    Counter: Integer;
      { counts off the multiplications or divisions needed to scale the
        result to the proper size }
  begin
    Assert (Operand <> nil, UninitializedRatioException,  
            RatioExceptionHandler);
    if ZeroNatural (Operand^.Numerator) then
      RatioToPascalReal := 0.0
    else begin
      Exponent := 0;
      AssignNatural (Top, Operand^.Numerator);
      AssignNatural (Bottom, Operand^.Denominator);
      while LessNatural (Bottom, Top) do begin
        Temporary := TwiceNatural (Bottom);
        DeallocateNatural (Bottom);
        Bottom := Temporary;
        Exponent := Exponent + 1
      end;
      while LessNatural (Top, Bottom) do begin
        Temporary := TwiceNatural (Top);
        DeallocateNatural (Top);
        Top := Temporary;
        Exponent := Exponent - 1
      end;
      Mantissa := 1.0;
      Temporary := SubtractNatural (Top, Bottom);
      DeallocateNatural (Top);
      Top := TwiceNatural (Temporary);
      DeallocateNatural (Temporary);
      PowerOfTwo := 1.0;
      BitNumber := 1;
      while BitNumber <= MantissaBits do begin
        PowerOfTwo := PowerOfTwo / 2;
        if GreaterOrEqualNatural (Top, Bottom) then begin
          Temporary := SubtractNatural (Top, Bottom);
          DeallocateNatural (Top);
          Mantissa := Mantissa + PowerOfTwo;
          Top := TwiceNatural (Temporary);
          DeallocateNatural (Temporary)
        end
        else begin
          Temporary := TwiceNatural (Top);
          DeallocateNatural (Top);
          Top := Temporary
        end;
        BitNumber := Bitnumber + 1
      end;
      if GreaterOrEqualNatural (Top, Bottom) then
        Mantissa := Mantissa + PowerOfTwo;
      DeallocateNatural (Top);
      DeallocateNatural (Bottom);
      Result := Mantissa;
      for Counter := 1 to Exponent do
        Result := Result * 2;
      for Counter := 1 to -Exponent do
        Result := Result / 2;
      if Operand^.Sign = Negative then
        RatioToPascalReal := -Result
      else
        RatioToPascalReal := Result
    end
  end;

  procedure DeallocateRatio (var R: Ratio);
  begin
    Assert (R <> nil, UninitializedRatioException, RatioExceptionHandler);
    DeallocateNatural (R^.Numerator);
    DeallocateNatural (R^.Denominator);
    Dispose (R);
    R := nil
  end;

end.

created November 8, 1996
last revised November 15, 1996

John David Stone (stone@math.grin.edu)