⚠️ Warning: This is a draft ⚠️

This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.

If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.

{{Rational Arithmetic}}

This implementation implements the task in a module named Frac that exports an opaque type named T, a standard Modula-3 practice. Although the task does not require it, the module also defines the exception ZeroDenominator for occasions when one might attempt division by zero, which includes at initialization. It provides two functions that return rational numbers for 0 and 1.

Unfortunately IMHO Modula-3 eschewed operator overloading, so the arithmetic looks unpleasant.

;Interface module

INTERFACE Frac;

EXCEPTION ZeroDenominator;

TYPE

  T <: Public;
  Public = OBJECT
  METHODS
    (* initialization and conversion *)
    init(a, b: INTEGER): T RAISES { ZeroDenominator };
    fromInt(a: INTEGER): T;
    (* unary operators *)
    abs(): T;
    opposite(): T;
    (* binary operators *)
    plus(other: T): T;
    minus(other: T): T;
    times(other: T): T;
    dividedBy(other: T): T RAISES { ZeroDenominator };
    integerDivision(other: INTEGER): T RAISES { ZeroDenominator };
    (* relations *)
    equals(other: T): BOOLEAN;
    notEqualTo(other: T): BOOLEAN;
    lessThan(other: T): BOOLEAN;
    lessEqual(other: T): BOOLEAN;
    greaterEqual(other: T): BOOLEAN;
    greater(other: T): BOOLEAN;
    (* other easily-checked properties *)
    isInt(): BOOLEAN;
    (* inc/decrement *)
    inc(step: CARDINAL := 1);
    dec(step: CARDINAL := 1);
  END;

  PROCEDURE One():  T;
  PROCEDURE Zero(): T;

  (* I/O *)
  PROCEDURE PutRat(a: T);

END Frac.

;Implementation module The implementation keeps all rational numbers in simplest form.

MODULE Frac;

IMPORT IO;

TYPE

  REVEAL T = Public BRANDED OBJECT
    num: INTEGER := 0;
    den: INTEGER := 1;
  OVERRIDES
    init := Init;
    fromInt := FromInt;
    abs := Abs;
    opposite := Opposite;
    plus := Plus;
    minus := Minus;
    times := Times;
    dividedBy := DividedBy;
    integerDivision := IntegerDivision;
    equals := Equals;
    notEqualTo := NotEqualTo;
    lessThan := LessThan;
    lessEqual := LessEqual;
    greaterEqual := GreaterEqual;
    greater := Greater;
    isInt := IsInt;
    inc := Inc;
    dec := Dec;
  END;

PROCEDURE Gcd(a, b: CARDINAL): CARDINAL =
VAR
  m := MAX(a, b);
  n := MIN(a, b);
  r: CARDINAL;
BEGIN
  WHILE n # 0 DO
    r := m MOD n;
    m := n;
    n := r;
  END;
  RETURN m;
END Gcd;

PROCEDURE Simplify(VAR a: T) =
VAR
  d := Gcd(ABS(a.num), ABS(a.den));
BEGIN
  a.num := a.num DIV d;
  a.den := a.den DIV d;
END Simplify;

PROCEDURE Init(self: T; a, b: INTEGER): T RAISES { ZeroDenominator } =
BEGIN
  IF (b > 0) THEN
    self.num := a;
    self.den := b;
  ELSIF (b < 0) THEN
    self.num := -a;
    self.den := -b;
  ELSE
    RAISE ZeroDenominator;
  END;
  Simplify(self);
  RETURN self;
END Init;

PROCEDURE FromInt(self: T; a: INTEGER): T =
BEGIN
  self.num := a;
  self.den := 1;
  RETURN self;
END FromInt;

PROCEDURE Abs(self: T): T =
BEGIN
  RETURN NEW(T, num := ABS(self.num), den := self.den);
END Abs;

PROCEDURE Opposite(self: T): T =
BEGIN
  RETURN NEW(T, num := -self.num, den := self.den);
END Opposite;

PROCEDURE Plus(self, other: T): T =
VAR
  result := NEW( T,
      num := self.num * other.den + self.den * other.num,
      den := self.den * other.den
  );
BEGIN
  Simplify(result);
  RETURN result;
END Plus;

PROCEDURE Minus(self, other: T): T =
VAR
  result := NEW( T,
      num := self.num * other.den - self.den * other.num,
      den := self.den * other.den
  );
BEGIN
  Simplify(result);
  RETURN result;
END Minus;

PROCEDURE Times(self, other: T): T =
VAR
  result := NEW( T,
      num := self.num * other.num,
      den := self.den * other.den
  );
BEGIN
  Simplify(result);
  RETURN result;
END Times;

PROCEDURE DividedBy(self, other: T): T RAISES { ZeroDenominator } =
VAR
  result := NEW( T,
      num := self.num * other.den,
      den := self.den * other.num
  );
BEGIN
  IF result.den = 0 THEN RAISE ZeroDenominator; END;
  IF result.den < 0 THEN
    result.num := -result.num;
    result.den := -result.den;
  END;
  Simplify(result);
  RETURN result;
END DividedBy;

PROCEDURE IntegerDivision(self: T; other: INTEGER): T
RAISES { ZeroDenominator } =
VAR
  result := NEW( T, num := self.num, den := self.den * other );
BEGIN
  IF other = 0 THEN RAISE ZeroDenominator; END;
  Simplify(result);
  RETURN result;
END IntegerDivision;

PROCEDURE Equals(self, other: T): BOOLEAN =
BEGIN
  (* this trick works only because we simplify after each operation *)
  RETURN (self.num = other.num) AND (self.den = other.den);
END Equals;

PROCEDURE NotEqualTo(self, other: T): BOOLEAN =
BEGIN
  (* this trick works only because we simplify after each operation *)
  RETURN (self.num # other.num) OR (self.den # other.den);
END NotEqualTo;

PROCEDURE LessThan(self, other: T): BOOLEAN =
BEGIN
  RETURN self.num * other.den < self.den * other.num;
END LessThan;

PROCEDURE LessEqual(self, other: T): BOOLEAN =
BEGIN
  RETURN self.num * other.den <= self.den * other.num;
END LessEqual;

PROCEDURE GreaterEqual(self, other: T): BOOLEAN =
BEGIN
  RETURN self.num * other.den >= self.den * other.num;
END GreaterEqual;

PROCEDURE Greater(self, other: T): BOOLEAN =
BEGIN
  RETURN self.num * other.den > self.den * other.num;
END Greater;

PROCEDURE Inc(self: T; step: CARDINAL) =
BEGIN
  INC(self.num, step * self.den);
END Inc;

PROCEDURE Dec(self: T; step: CARDINAL) =
BEGIN
  DEC(self.num, step * self.den);
END Dec;

PROCEDURE IsInt(self: T): BOOLEAN =
BEGIN
  RETURN self.den = 1;
END IsInt;

PROCEDURE One(): T =
BEGIN
  TRY
    RETURN NEW(T).init(1, 1);
  EXCEPT ZeroDenominator =>
    IO.Put("Something went very wrong.\n");
    RETURN NIL;
  END;
END One;

PROCEDURE Zero(): T =
BEGIN
  TRY
    RETURN NEW(T).init(0, 1);
  EXCEPT ZeroDenominator =>
    IO.Put("Something went very wrong.\n");
    RETURN NIL;
  END;
END Zero;

PROCEDURE PutRat(a: T) =
BEGIN
  IO.PutInt(a.num);
  IF a.den # 1 THEN
    IO.Put(" / "); IO.PutInt(a.den);
  END;
END PutRat;

BEGIN
END Frac.

;Test Program

{{Trans|Nim}} This module performs a few additional tests. The test for perfect numbers is based on that of Nim above.

MODULE TestRational EXPORTS Main;

IMPORT IO, Frac AS R, Word;

FROM Math IMPORT sqrt;

PROCEDURE PutBool(b: BOOLEAN) =
BEGIN
  IF b THEN IO.Put("true");
  ELSE IO.Put("false");
  END;
END PutBool;

VAR

  a, b: R.T;

  n: Word.T := 2;
  limit: Word.T := 1;

BEGIN

  TRY
    a := NEW(R.T).init(2,3);
    b := NEW(R.T).init(-3,4);
  EXCEPT | R.ZeroDenominator =>
    IO.Put("Zero division!\n");
  END;

  R.PutRat(a); IO.Put(" op "); R.PutRat(b); IO.Put(" = \n");

  IO.Put("  + : "); R.PutRat(a.plus(b));  IO.PutChar('\n');
  IO.Put("  - : "); R.PutRat(a.minus(b)); IO.PutChar('\n');
  IO.Put("  * : "); R.PutRat(a.times(b)); IO.PutChar('\n');

  TRY
    IO.Put("  /: "); R.PutRat(a.dividedBy(b)); IO.PutChar('\n');
  EXCEPT | R.ZeroDenominator =>
    IO.Put("Zero division\n");
  END;

  IO.Put("  <  : "); PutBool(a.lessThan(b));     IO.PutChar('\n');
  IO.Put("  <= : "); PutBool(a.lessEqual(b));    IO.PutChar('\n');
  IO.Put("  >= : "); PutBool(a.greaterEqual(b)); IO.PutChar('\n');
  IO.Put("  >  : "); PutBool(a.greater(b));      IO.PutChar('\n');

  IO.PutChar('\n');

  IO.Put("Increasing "); R.PutRat(a); IO.Put(" by\n");
  IO.Put("  1 gives "); a.inc(1); R.PutRat(a); IO.PutChar('\n');
  IO.Put("  3 gives "); a.inc(3); R.PutRat(a); IO.PutChar('\n');

  IO.Put("Decreasing "); R.PutRat(a); IO.Put(" by\n");
  IO.Put("  1 gives "); a.dec(1); R.PutRat(a); IO.PutChar('\n');
  IO.Put("  3 gives "); a.dec(3); R.PutRat(a); IO.PutChar('\n');

  limit := Word.LeftShift(limit, 19);
  TRY
    WHILE n < limit DO
        VAR
          sum := NEW(R.T).init(1, n);
          maxFac := FLOOR(sqrt(FLOAT(n, LONGREAL)));
        BEGIN
          FOR i := 2 TO maxFac DO
            IF n MOD i = 0 THEN
              sum := sum.plus(NEW(R.T).init(1, i))
                        .plus(NEW(R.T).init(1, n DIV i));
            END;
          END;
          IF sum.isInt() THEN
            IO.Put("sum of reciprocal factors of "); IO.PutInt(n);
            IO.Put(" is exactly "); R.PutRat(sum);
            IF sum.equals(R.One()) THEN
              IO.Put(" perfect!");
            END;
            IO.PutChar('\n');
          END;
        END;
      INC(n, 2);
    END;
  EXCEPT R.ZeroDenominator =>
    IO.Put("Something went very wrong\n");
  END;

END TestRational.

{{out}}


2 / 3 op -3 / 4 = 
  + : -1 / 12
  - : 17 / 12
  * : -1 / 2
  /: -8 / 9
  <  : false
  <= : false
  >= : true
  >  : true

Increasing 2 / 3 by
  1 gives 5 / 3
  3 gives 14 / 3
Decreasing 14 / 3 by
  1 gives 11 / 3
  3 gives 2 / 3
sum of reciprocal factors of 6 is exactly 1 perfect!
sum of reciprocal factors of 28 is exactly 1 perfect!
sum of reciprocal factors of 120 is exactly 2
sum of reciprocal factors of 496 is exactly 1 perfect!
sum of reciprocal factors of 672 is exactly 2
sum of reciprocal factors of 8128 is exactly 1 perfect!
sum of reciprocal factors of 30240 is exactly 3
sum of reciprocal factors of 32760 is exactly 3
sum of reciprocal factors of 523776 is exactly 2