# Arithmetic/Rational/Modula-3

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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.

Interface module

<lang modula3>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.</lang>

Implementation module

The implementation keeps all rational numbers in simplest form.

<lang modula3>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.</lang>

Test Program
Translation of: Nim

This module performs a few additional tests. The test for perfect numbers is based on that of Nim above. <lang modula3>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.</lang>

Output:
```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
```