GENERIC MODULEFractionBasic (R, GCD);
Arithmetic for Modula-3, see doc for detailsAbstract: Fraction numbers and basic operations
IMPORT Arithmetic AS Arith; <* UNUSED *> CONST Module = "FractionBasic."; PROCEDURECancel (READONLY x: T; ): T RAISES {Arith.Error} = VAR gcd := GCD.GCD(x.n, x.d); BEGIN RETURN T{R.Div(x.n, gcd), R.Div(x.d, gcd)}; END Cancel; PROCEDUREAdd (READONLY x, y: T; ): T = VAR gcd := GCD.GCD(x.d, y.d); xdc := R.Div(x.d, gcd); ydc := R.Div(y.d, gcd); z := T{n := R.Add(R.Mul(x.n, ydc), R.Mul(y.n, xdc)), d := R.Mul(xdc, y.d) (* least common multiple*)}; <* FATAL Arith.Error *> (* Division will always succeed*) BEGIN RETURN Cancel(z); (* final cancellation is necessary as the example 1/2+1/2 shows, if the denominators are different it may be unnecessary*) END Add; PROCEDURESub (READONLY x, y: T; ): T = VAR gcd := GCD.GCD(x.d, y.d); xdc := R.Div(x.d, gcd); ydc := R.Div(y.d, gcd); z := T{n := R.Sub(R.Mul(x.n, ydc), R.Mul(y.n, xdc)), d := R.Mul(xdc, y.d) (* least common multiple*)}; <* FATAL Arith.Error *> (* Division will always succeed*) BEGIN RETURN Cancel(z); END Sub; PROCEDURENeg (READONLY x: T; ): T = BEGIN RETURN T{R.Neg(x.n), x.d}; END Neg; PROCEDUREConj (READONLY x: T; ): T = BEGIN RETURN x; END Conj; PROCEDUREIsZero (READONLY x: T; ): BOOLEAN = BEGIN RETURN R.IsZero(x.n); END IsZero; PROCEDUREEqual (READONLY x, y: T; ): BOOLEAN = BEGIN (* comparing component-wise may fail if the field has more than one unit (say e.g. -1), in this case the fraction representation is not unique!*) RETURN R.Equal(R.Mul(x.n, y.d), R.Mul(y.n, x.d)); END Equal; PROCEDURECompare (READONLY x, y: T; ): [-1 .. 1] = BEGIN RETURN R.Compare(R.Mul(x.n, y.d), R.Mul(y.n, x.d)); END Compare; PROCEDUREMul (READONLY x, y: T; ): T = VAR gcd := GCD.GCD(x.n, y.d); z : T; <* FATAL Arith.Error *> (* Division will always succeed*) BEGIN z.n := R.Div(x.n, gcd); z.d := R.Div(y.d, gcd); gcd := GCD.GCD(y.n, x.d); z.n := R.Mul(z.n, R.Div(y.n, gcd)); z.d := R.Mul(z.d, R.Div(x.d, gcd)); RETURN z; END Mul; PROCEDUREDiv (READONLY x, y: T; ): T RAISES {Arith.Error} = VAR gcd := GCD.GCD(x.n, y.n); z : T; BEGIN z.n := R.Div(x.n, gcd); z.d := R.Div(y.n, gcd); gcd := GCD.GCD(y.d, x.d); z.n := R.Mul(z.n, R.Div(y.d, gcd)); z.d := R.Mul(z.d, R.Div(x.d, gcd)); RETURN z; END Div; PROCEDURERec (READONLY x: T; ): T RAISES {Arith.Error} = BEGIN IF R.IsZero(x.n) THEN RAISE Arith.Error(NEW(Arith.ErrorDivisionByZero).init()); END; RETURN T{x.d, x.n}; END Rec; PROCEDUREMod (<* UNUSED *> READONLY x: T; READONLY y: T; ): T RAISES {Arith.Error} (* return x mod y*) = BEGIN IF R.IsZero(y.n) THEN RAISE Arith.Error(NEW(Arith.ErrorDivisionByZero).init()); END; RETURN Zero; END Mod; PROCEDUREDivMod (x, y: T; ): QuotRem RAISES {Arith.Error} = BEGIN RETURN QuotRem{Div(x, y), Zero}; END DivMod; PROCEDUREIntMod (READONLY x, y: T; ): T RAISES {Arith.Error} = VAR gcd := GCD.GCD(x.d, y.d); xdc := R.Div(x.d, gcd); ydc := R.Div(y.d, gcd); z := T{n := R.Mod(R.Mul(x.n, ydc), R.Mul(y.n, xdc)), d := R.Mul(xdc, y.d) (* least common multiple*)}; BEGIN RETURN Cancel(z); END IntMod; PROCEDURESquare (READONLY x: T; ): T = BEGIN RETURN T{R.Mul(x.n, x.n), R.Mul(x.d, x.d)}; (*RETURN T{R.Square(x.n),R.Square(x.d)};*) END Square; PROCEDUREScale (READONLY x: T; y: R.T; ): T = <* FATAL Arith.Error *> BEGIN RETURN Cancel(T{R.Mul(x.n, y), x.d}); (*RETURN T{R.Scale(x.n,y),x.d};*) END Scale; BEGIN Zero := T{n := R.Zero, d := R.One}; One := T{n := R.One, d := R.One}; (* MinusOne := T{n:=R.MinusOne, d:=R.One}; *) END FractionBasic.