
(********************************************************************)
(*                                                                  *)
(*  rational.s7i  Rational number support library                   *)
(*  Copyright (C) 1991 - 1994, 2005, 2007  Thomas Mertes            *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


(**
 *  Rational numbers represented with 'integer' numerator and denominator.
 *  The values of the type 'rational' are finite and periodical
 *  decimal numbers. Rational literals do not exist. The result of a
 *  'rational' operation is undefined when it overflows.
 *)
const type: rational is new object struct
    var integer: numerator is 0;
    var integer: denominator is 1;
  end struct;


const proc: normalize (inout rational: number) is func
  begin
    if number.denominator < 0 then
      number.numerator := -number.numerator;
      number.denominator := -number.denominator;
    end if;
  end func;


const proc: reduce (inout rational: number) is func
  local
    var integer: a is 0;
    var integer: b is 0;
    var integer: help is 0;
  begin
    if number.numerator >= 0 then
      a := number.numerator;
    else
      a := -number.numerator;
    end if;
    b := number.denominator;
    while a <> 0 do
      help := b rem a;
      b := a;
      a := help;
    end while;
    number.numerator := number.numerator div b;
    number.denominator := number.denominator div b;
  end func;


const func integer: gcd1 (in var integer: a, in var integer: b) is func
  result
    var integer: result is 0;
  local
    var integer: help is 0;
  begin
    while a <> 0 do
      help := b rem a;
      b := a;
      a := help;
    end while;
    result := b;
  end func;


const func integer: gcd2 (in integer: numerator, in integer: denominator) is func
  result
    var integer: b is 0;
  local
    var integer: a is 0;
    var integer: help is 0;
  begin
    if numerator >= 0 then
      a := numerator;
    else
      a := -numerator;
    end if;
    b := denominator;
    while a <> 0 do
      help := b rem a;
      b := a;
      a := help;
    end while;
  end func;


(**
 *  Create a rational number from its numerator and denominator.
 *  @return the created rational value.
 *)
const func rational: (in integer: numerator) / (in integer: denominator) is func
  result
    var rational: result is rational.value;
  begin
    result.numerator := numerator;
    result.denominator := denominator;
    normalize(result);
    reduce(result);
  end func;


(**
 *  Plus sign for rational numbers.
 *  @return its operand unchanged.
 *)
const func rational: + (in rational: number) is func
  result
    var rational: result is rational.value;
  begin
    result.numerator := number.numerator;
    result.denominator := number.denominator;
  end func;


(**
 *  Minus sign, negate a rational number.
 *  @return the negated value of the number.
 *)
const func rational: - (in rational: number) is func
  result
    var rational: negatedValue is rational.value;
  begin
    negatedValue.numerator := -number.numerator;
    negatedValue.denominator := number.denominator;
  end func;


(**
 *  Add two rational numbers.
 *  @return the sum of the two numbers.
 *)
const func rational: (in rational: summand1) + (in rational: summand2) is func
  result
    var rational: sum is rational.value;
  local
    var integer: gcd_denominator is 0;
  begin
    gcd_denominator := gcd1(summand1.denominator, summand2.denominator);
    sum.numerator := (summand1.numerator * summand2.denominator +
        summand2.numerator * summand1.denominator) div gcd_denominator;
    sum.denominator := summand1.denominator div gcd_denominator * summand2.denominator;
  end func;


(**
 *  Compute the subtraction of two rational numbers.
 *  @return the difference of the two numbers.
 *)
const func rational: (in rational: minuend) - (in rational: subtrahend) is func
  result
    var rational: difference is rational.value;
  local
    var integer: gcd_denominator is 0;
  begin
    gcd_denominator := gcd1(minuend.denominator, subtrahend.denominator);
    difference.numerator := (minuend.numerator * subtrahend.denominator -
        subtrahend.numerator * minuend.denominator) div gcd_denominator;
    difference.denominator := minuend.denominator div gcd_denominator * subtrahend.denominator;
  end func;


(**
 *  Multiply two rational numbers.
 *  @return the product of the two numbers.
 *)
const func rational: (in rational: factor1) * (in rational: factor2) is func
  result
    var rational: product is rational.value;
  local
    var integer: gcd1 is 0;
    var integer: gcd2 is 0;
  begin
    gcd1 := gcd2(factor1.numerator, factor2.denominator);
    gcd2 := gcd2(factor2.numerator, factor1.denominator);
    product.numerator := (factor1.numerator div gcd1) * (factor2.numerator div gcd2);
    product.denominator := (factor1.denominator div gcd2) * (factor2.denominator div gcd1);
  end func;


(**
 *  Compute the division of two rational numbers.
 *  @return the quotient of the division.
 *  @exception NUMERIC_ERROR When a division by zero occurs.
 *)
const func rational: (in rational: dividend) / (in rational: divisor) is func
  result
    var rational: quotient is rational.value;
  begin
    quotient.numerator := dividend.numerator * divisor.denominator;
    quotient.denominator := dividend.denominator * divisor.numerator;
    normalize(quotient);
    reduce(quotient);
  end func;


(**
 *  Increment a rational number by a delta.
 *)
const proc: (inout rational: number) +:= (in rational: delta) is func
  local
    var integer: gcd_denominator is 0;
  begin
    gcd_denominator := gcd1(number.denominator, delta.denominator);
    number.numerator := (number.numerator * delta.denominator +
        delta.numerator * number.denominator) div gcd_denominator;
    number.denominator *:= delta.denominator div gcd_denominator;
  end func;


(**
 *  Decrement a rational number by a delta.
 *)
const proc: (inout rational: number) -:= (in rational: delta) is func
  local
    var integer: gcd_denominator is 0;
  begin
    gcd_denominator := gcd1(number.denominator, delta.denominator);
    number.numerator := (number.numerator * delta.denominator -
        delta.numerator * number.denominator) div gcd_denominator;
    number.denominator *:= delta.denominator div gcd_denominator;
  end func;


(**
 *  Multiply a rational number by a factor and assign the result back to number.
 *)
const proc: (inout rational: number) *:= (in rational: factor) is func
  begin
    number.numerator *:= factor.numerator;
    number.denominator *:= factor.denominator;
    reduce(number);
  end func;


(**
 *  Divide a rational number by a divisor and assign the result back to number.
 *)
const proc: (inout rational: number) /:= (in rational: divisor) is func
  begin
    number.numerator *:= divisor.denominator;
    number.denominator *:= divisor.numerator;
    normalize(number);
    reduce(number);
  end func;


(**
 *  Compute the exponentiation of a rational base by an integer exponent.
 *  @return the result of the exponentation.
 *)
const func rational: (in rational: base) ** (in integer: exponent) is func
  result
    var rational: power is rational.value;
  begin
    if exponent >= 0 then
      power.numerator := base.numerator ** exponent;
      power.denominator := base.denominator ** exponent;
    else
      power.numerator := base.denominator ** (-exponent);
      power.denominator := base.numerator ** (-exponent);
      normalize(power);
    end if;
  end func;


(**
 *  Check if two rational numbers are equal.
 *  @return TRUE if both numbers are equal,
 *          FALSE otherwise.
 *)
const func boolean: (in rational: number1) = (in rational: number2) is
  return number1.numerator   = number2.numerator and
         number1.denominator = number2.denominator;


(**
 *  Check if two rational numbers are not equal.
 *  @return FALSE if both numbers are equal,
 *          TRUE otherwise.
 *)
const func boolean: (in rational: number1) <> (in rational: number2) is
  return number1.numerator   <> number2.numerator or
         number1.denominator <> number2.denominator;


(**
 *  Check if number1 is less than number2.
 *  @return TRUE if number1 is less than number2,
 *          FALSE otherwise.
 *)
const func boolean: (in rational: number1) < (in rational: number2) is
  return number1.numerator * number2.denominator <
         number2.numerator * number1.denominator;


(**
 *  Check if number1 is greater than number2.
 *  @return TRUE if number1 is greater than number2,
 *          FALSE otherwise.
 *)
const func boolean: (in rational: number1) > (in rational: number2) is
  return number1.numerator * number2.denominator >
         number2.numerator * number1.denominator;


(**
 *  Check if number1 is less than or equal to number2.
 *  @return TRUE if number1 is less than or equal to number2,
 *          FALSE otherwise.
 *)
const func boolean: (in rational: number1) <= (in rational: number2) is
  return number1.numerator * number2.denominator <=
         number2.numerator * number1.denominator;


(**
 *  Check if number1 is greater than or equal to number2.
 *  @return TRUE if number1 is greater than or equal to number2,
 *          FALSE otherwise.
 *)
const func boolean: (in rational: number1) >= (in rational: number2) is
  return number1.numerator * number2.denominator >=
         number2.numerator * number1.denominator;


(**
 *  Compare two rational numbers.
 *  @return -1, 0 or 1 if the first argument is considered to be
 *          respectively less than, equal to, or greater than the
 *          second.
 *)
const func integer: compare (in rational: number1, in rational: number2) is func
  result
    var integer: result is 0;
  begin
    if number1.numerator * number2.denominator <
        number2.numerator * number1.denominator then
      result := -1;
    elsif number1.numerator * number2.denominator >
        number2.numerator * number1.denominator then
      result := 1;
    end if;
  end func;


(**
 *  Compute the hash value of a rational number.
 *  @return the hash value.
 *)
const func integer: hashCode (in rational: number) is
  return number.numerator * number.denominator;


(**
 *  Return the conversion of an integer to a rational.
 *)
const func rational: rat (in integer: number) is func
  result
    var rational: result is rational.value;
  begin
    result.numerator := number;
    result.denominator := 1;
  end func;


(**
 *  Return the conversion of an integer to a rational.
 *)
const func rational: (attr rational) conv (ref integer: number) is func
  result
    var rational: result is rational.value;
  begin
    result.numerator := number;
    result.denominator := 1;
  end func;


(**
 *  Compute the absolute value of a rational number.
 *  @return the absolute value.
 *)
const func rational: abs (in rational: number) is func
  result
    var rational: absoluteValue is rational.value;
  begin
    absoluteValue.numerator := abs(number.numerator);
    absoluteValue.denominator := number.denominator;
  end func;


(**
 *  Convert a rational number to a string.
 *  The number is converted to a string with a fraction (e.g.: "3/5"),
 *  were numerator and denominator are separated with a slash (/).
 *  @return the string result of the conversion.
 *)
const func string: str (in rational: number) is func
  result
    var string: result is "";
  begin
    result := str(number.numerator) & "/" & str(number.denominator);
  end func;


(**
 *  Convert a rational number to a string.
 *  The number is converted to a string with decimal representation.
 *  The precision parameter specifies the number of digits after
 *  the decimal point.
 *  @return the string result of the conversion.
 *)
const func string: (in rational: number) digits (in integer: precision) is func
  result
    var string: result is "";
  begin
    result := str((number.numerator * 10 ** precision) div number.denominator);
    if precision + 1 - length(result) > 0 then
      result := "0" mult (precision + 1 - length(result)) & result;
    end if;
    result := result[ .. length(result) - precision] & "." &
        result[length(result) - precision + 1 .. ];
  end func;


(**
 *  Convert a string to a rational number.
 *  The string must contain a fraction (e.g.: "3/5"), were numerator
 *  and denominator are separated with a slash (/).
 *  @return the rational result of the conversion.
 *  @exception RANGE_ERROR When stri contains not a valid rational value.
 *)
const func rational: (attr rational) parse (in var string: stri) is func
  result
    var rational: result is rational.value;
  begin
    result.numerator := integer parse getint(stri);
    if stri[1] <> '/' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.denominator := integer parse getint(stri);
  end func;


enable_io(rational);


(**
 *  Return a rational number truncated towards negative infinity.
 *)
const func integer: floor (ref rational: number) is func
  result
    var integer: int_val is 0;
  begin
    int_val := number.numerator mdiv number.denominator;
  end func;


(**
 *  Return a rational number rounded up towards positive infinity.
 *)
const func integer: ceil (ref rational: number) is func
  result
    var integer: int_val is 0;
  begin
    int_val := -(number.numerator mdiv -number.denominator);
  end func;


(**
 *  Return a rational number truncated towards zero.
 *)
const func integer: trunc (ref rational: number) is func
  result
    var integer: int_val is 0;
  begin
    int_val := number.numerator div number.denominator;
  end func;


(**
 *  Return a rational number rounded towards zero.
 *)
const func integer: round (ref rational: number) is func
  result
    var integer: int_val is 0;
  begin
    if number.numerator >= 0 then
      int_val := (2 * number.numerator + number.denominator) div (2 * number.denominator);
    else
      int_val := (2 * number.numerator - number.denominator) div (2 * number.denominator);
    end if;
  end func;
