
(********************************************************************)
(*                                                                  *)
(*  bigrat.s7i    Big rational number support library               *)
(*  Copyright (C) 2006, 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.                       *)
(*                                                                  *)
(********************************************************************)


const type: bigRational is new object struct
    var bigInteger: numerator is 0_;
    var bigInteger: denominator is 1_;
  end struct;


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


const proc: reduce (inout bigRational: rat) is func
  local
    var bigInteger: gcd is 0_;
  begin
    gcd := gcd(rat.numerator, rat.denominator);
    rat.numerator := rat.numerator div gcd;
    rat.denominator := rat.denominator div gcd;
  end func;


(**
 *  Return the given bigRational number.
 *)
const func bigRational: + (in bigRational: rat) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    rat_val.numerator := rat.numerator;
    rat_val.denominator := rat.denominator;
  end func;


(**
 *  Return the negated value of a bigRational number.
 *)
const func bigRational: - (in bigRational: rat) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    rat_val.numerator := -rat.numerator;
    rat_val.denominator := rat.denominator;
  end func;


(**
 *  Return the sum of two bigRational numbers.
 *)
const func bigRational: (in bigRational: rat1) + (in bigRational: rat2) is func
  result
    var bigRational: rat_val is bigRational.value;
  local
    var bigInteger: gcd_denominator is 0_;
  begin
    gcd_denominator := gcd(rat1.denominator, rat2.denominator);
    rat_val.numerator := (rat1.numerator * rat2.denominator +
        rat2.numerator * rat1.denominator) div gcd_denominator;
    rat_val.denominator := rat1.denominator div gcd_denominator * rat2.denominator;
  end func;


(**
 *  Return the difference of two bigRational numbers.
 *)
const func bigRational: (in bigRational: rat1) - (in bigRational: rat2) is func
  result
    var bigRational: rat_val is bigRational.value;
  local
    var bigInteger: gcd_denominator is 0_;
  begin
    gcd_denominator := gcd(rat1.denominator, rat2.denominator);
    rat_val.numerator := (rat1.numerator * rat2.denominator -
        rat2.numerator * rat1.denominator) div gcd_denominator;
    rat_val.denominator := rat1.denominator div gcd_denominator * rat2.denominator;
  end func;


(**
 *  Return the product of two bigRational numbers.
 *)
const func bigRational: (in bigRational: rat1) * (in bigRational: rat2) is func
  result
    var bigRational: rat_val is bigRational.value;
  local
    var bigInteger: gcd1 is 0_;
    var bigInteger: gcd2 is 0_;
  begin
    gcd1 := gcd(rat1.numerator, rat2.denominator);
    gcd2 := gcd(rat2.numerator, rat1.denominator);
    rat_val.numerator := (rat1.numerator div gcd1) * (rat2.numerator div gcd2);
    rat_val.denominator := (rat1.denominator div gcd2) * (rat2.denominator div gcd1);
  end func;


(**
 *  Return the quotient of two bigRational numbers.
 *)
const func bigRational: (in bigRational: rat1) / (in bigRational: rat2) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    rat_val.numerator := rat1.numerator * rat2.denominator;
    rat_val.denominator := rat1.denominator * rat2.numerator;
    normalize(rat_val);
    reduce(rat_val);
  end func;


(**
 *  Increment the bigRational number rat1 by rat2.
 *)
const proc: (inout bigRational: rat1) +:= (in bigRational: rat2) is func
  local
    var bigInteger: gcd_denominator is 0_;
  begin
    gcd_denominator := gcd(rat1.denominator, rat2.denominator);
    rat1.numerator := (rat1.numerator * rat2.denominator +
        rat2.numerator * rat1.denominator) div gcd_denominator;
    rat1.denominator *:= rat2.denominator div gcd_denominator;
  end func;


(**
 *  Decrement the bigRational number rat1 by rat2.
 *)
const proc: (inout bigRational: rat1) -:= (in bigRational: rat2) is func
  local
    var bigInteger: gcd_denominator is 0_;
  begin
    gcd_denominator := gcd(rat1.denominator, rat2.denominator);
    rat1.numerator := (rat1.numerator * rat2.denominator -
        rat2.numerator * rat1.denominator) div gcd_denominator;
    rat1.denominator *:= rat2.denominator div gcd_denominator;
  end func;


(**
 *  Multiply the bigRational number rat1 by rat2 and assign the
 *  result to rat1.
 *)
const proc: (inout bigRational: rat1) *:= (in bigRational: rat2) is func
  begin
    rat1.numerator *:= rat2.numerator;
    rat1.denominator *:= rat2.denominator;
    reduce(rat1);
  end func;


(**
 *  Create a bigRational number from its numerator and denominator.
 *)
const func bigRational: (in bigInteger: int1) / (in bigInteger: int2) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    rat_val.numerator := int1;
    rat_val.denominator := int2;
    normalize(rat_val);
    reduce(rat_val);
  end func;


(**
 *  Return rat1 to the power of int2.
 *)
const func bigRational: (in bigRational: rat1) ** (in integer: int2) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    if int2 >= 0 then
      rat_val.numerator := rat1.numerator ** int2;
      rat_val.denominator := rat1.denominator ** int2;
    else
      rat_val.numerator := rat1.denominator ** (-int2);
      rat_val.denominator := rat1.numerator ** (-int2);
      normalize(rat_val);
    end if;
  end func;


const func boolean: (in bigRational: rat1) = (in bigRational: rat2) is
  return rat1.numerator   = rat2.numerator and
         rat1.denominator = rat2.denominator;


const func boolean: (in bigRational: rat1) < (in bigRational: rat2) is
  return rat1.numerator * rat2.denominator <
         rat2.numerator * rat1.denominator;


const func boolean: (in bigRational: rat1) > (in bigRational: rat2) is
  return rat1.numerator * rat2.denominator >
         rat2.numerator * rat1.denominator;


const func boolean: (in bigRational: rat1) <= (in bigRational: rat2) is
  return rat1.numerator * rat2.denominator <=
         rat2.numerator * rat1.denominator;


const func boolean: (in bigRational: rat1) >= (in bigRational: rat2) is
  return rat1.numerator * rat2.denominator >=
         rat2.numerator * rat1.denominator;


const func boolean: (in bigRational: rat1) <> (in bigRational: rat2) is
  return rat1.numerator   <> rat2.numerator or
         rat1.denominator <> rat2.denominator;


(**
 *  Returns -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 bigRational: rat1, in bigRational: rat2) is func
  result
    var integer: result is 0;
  begin
    if rat1.numerator * rat2.denominator <
        rat2.numerator * rat1.denominator then
      result := -1;
    elsif rat1.numerator * rat2.denominator >
        rat2.numerator * rat1.denominator then
      result := 1;
    end if;
  end func;


(**
 *  Return the hash value of a bigRational.
 *)
const func integer: hashCode (in bigRational: rat) is
  return hashCode(rat.numerator) * hashCode(rat.denominator);


(**
 *  Return the conversion of a bigInteger to a bigRational.
 *)
const func bigRational: rat (in bigInteger: number) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    rat_val.numerator := number;
    rat_val.denominator := 1_;
  end func;


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


(**
 *  Return the conversion of a bigInteger to a bigRational.
 *)
const func bigRational: (attr bigRational) conv (in bigInteger: number) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    rat_val.numerator := number;
    rat_val.denominator := 1_;
  end func;


(**
 *  Return the absolute value of a bigRational number.
 *)
const func bigRational: abs (in bigRational: rat1) is func
  result
    var bigRational: rat_val is bigRational.value;
  begin
    rat_val.numerator := abs(rat1.numerator);
    rat_val.denominator := rat1.denominator;
  end func;


(**
 *  Return the conversion of a bigRational number to a string.
 *)
const func string: str (in bigRational: rat1) is func
  result
    var string: result is "";
  begin
    result := str(rat1.numerator) & "/" & str(rat1.denominator);
  end func;


(**
 *  Return the conversion of a bigRational number to a string.
 *  The numDigits parameter specifies the number of digits
 *  after the decimal point.
 *)
const func string: (in bigRational: rat1) digits (in integer: numDigits) is func
  result
    var string: result is "";
  begin
    result := str((rat1.numerator * 10_ ** numDigits) div rat1.denominator);
    if numDigits + 1 - length(result) > 0 then
      result := "0" mult (numDigits + 1 - length(result)) & result;
    end if;
    result := result[ .. length(result) - numDigits] & "." &
        result[length(result) - numDigits + 1 .. ];
  end func;


(**
 *  Return the conversion of a string to a bigRational number.
 *)
const func bigRational: (attr bigRational) parse (in var string: stri) is func
  result
    var bigRational: result is bigRational.value;
  begin
    result.numerator := bigInteger parse getint(stri);
    if stri[1] <> '/' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.denominator := bigInteger parse getint(stri);
  end func;


enable_io(bigRational);


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


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


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


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