Task

Create a reasonably complete implementation of rational arithmetic in the particular language using the idioms of the language.



Example

Define a new type called frac with binary operator "//" of two integers that returns a structure made up of the numerator and the denominator (as per a rational number).

Further define the appropriate rational unary operators abs and '-', with the binary operators for addition '+', subtraction '-', multiplication '×', division '/', integer division '÷', modulo division, the comparison operators (e.g. '<', '≤', '>', & '≥') and equality operators (e.g. '=' & '≠').

Define standard coercion operators for casting int to frac etc.

If space allows, define standard increment and decrement operators (e.g. '+:=' & '-:=' etc.).

Finally test the operators:
Use the new type frac to find all perfect numbers less than 219 by summing the reciprocal of the factors.



Related task

   Perfect Numbers


import std.bigint, std.traits, std.conv;
 
// std.numeric.gcd doesn't work with BigInt.
T gcd(T)(in T a, in T b) pure nothrow {
    return (b != 0) ? gcd(b, a % b) : (a < 0) ? -a : a;
}
 
T lcm(T)(in T a, in T b) pure nothrow {
    return a / gcd(a, b) * b;
}
 
struct RationalT(T) if (!isUnsigned!T) {
    private T num, den; // Numerator & denominator.
 
    private enum Type { NegINF = -2,
                        NegDEN = -1,
                        NaRAT  =  0,
                        NORMAL =  1,
                        PosINF =  2 };
 
    this(U : RationalT)(U n) pure nothrow {
        num = n.num;
        den = n.den;
    }
 
    this(U)(in U n) pure nothrow if (isIntegral!U) {
        num = toT(n);
        den = 1UL;
    }
 
    this(U, V)(in U n, in V d) pure nothrow {
        num = toT(n);
        den = toT(d);
        const common = gcd(num, den);
        if (common != 0) {
            num /= common;
            den /= common;
        } else { // infinite or NOT a Number
            num = (num == 0) ? 0 : (num < 0) ? -1 : 1;
            den = 0;
        }
        if (den < 0) { // Assure den is non-negative.
            num = -num;
            den = -den;
        }
    }
 
    static T toT(U)(in ref U n) pure nothrow if (is(U == T)) {
        return n;
    }
 
    static T toT(U)(in ref U n) pure nothrow if (!is(U == T)) {
        T result = n;
        return result;
    }
 
    T numerator() const pure nothrow @property {
        return num;
    }
 
    T denominator() const pure nothrow @property {
        return den;
    }
 
    string toString() const /*pure nothrow*/ {
        if (den != 0)
            return num.text ~ (den == 1 ? "" : "/" ~ den.text);
        if (num == 0)
            return "NaRat";
        else
            return ((num < 0) ? "-" : "+") ~ "infRat";
    }
 
    real toReal() pure const nothrow {
        static if (is(T == BigInt))
            return num.toLong / real(den.toLong);
        else
            return num / real(den);
    }
 
    RationalT opBinary(string op)(in RationalT r)
    const pure nothrow if (op == "+" || op == "-") {
        T common = lcm(den, r.den);
        T n = mixin("common / den * num" ~ op ~
                    "common / r.den * r.num" );
        return RationalT(n, common);
    }
 
    RationalT opBinary(string op)(in RationalT r)
    const pure nothrow if (op == "*") {
        return RationalT(num * r.num, den * r.den);
    }
 
    RationalT opBinary(string op)(in RationalT r)
    const pure nothrow if (op == "/") {
        return RationalT(num * r.den, den * r.num);
    }
 
    RationalT opBinary(string op, U)(in U r)
    const pure nothrow if (isIntegral!U && (op == "+" ||
                           op == "-" || op == "*" || op == "/")) {
        return opBinary!op(RationalT(r));
    }
 
    RationalT opBinary(string op)(in size_t p)
    const pure nothrow if (op == "^^") {
        return RationalT(num ^^ p, den ^^ p);
    }
 
    RationalT opBinaryRight(string op, U)(in U l)
    const pure nothrow if (isIntegral!U) {
        return RationalT(l).opBinary!op(RationalT(num, den));
    }
 
    RationalT opOpAssign(string op, U)(in U l) pure /*nothrow*/ {
        mixin("this = this " ~ op ~ "l;");
        return this;
    }
 
    RationalT opUnary(string op)()
    const pure nothrow if (op == "+" || op == "-") {
        return RationalT(mixin(op ~ "num"), den);
    }
 
    bool opCast(U)() const if (is(U == bool)) {
        return num != 0;
    }
 
    bool opEquals(U)(in U r) const pure nothrow {
        RationalT rhs = RationalT(r);
        if (type() == Type.NaRAT || rhs.type() == Type.NaRAT)
            return false;
        return num == rhs.num && den == rhs.den;
    }
 
    int opCmp(U)(in U r) const pure nothrow {
        auto rhs = RationalT(r);
        if (type() == Type.NaRAT || rhs.type() == Type.NaRAT)
            throw new Error("Compare involve a NaRAT.");
        if (type() != Type.NORMAL ||
            rhs.type() != Type.NORMAL) // for infinite
            return (type() == rhs.type()) ? 0 :
                ((type() < rhs.type()) ? -1 : 1);
        auto diff = num * rhs.den - den * rhs.num;
        return (diff == 0) ? 0 : ((diff < 0) ? -1 : 1);
    }
 
    Type type() const pure nothrow {
        if (den > 0) return Type.NORMAL;
        if (den < 0) return Type.NegDEN;
        if (num > 0) return Type.PosINF;
        if (num < 0) return Type.NegINF;
        return Type.NaRAT;
    }
}
 
RationalT!U rational(U)(in U n) pure nothrow {
    return typeof(return)(n);
}
 
RationalT!(CommonType!(U1, U2))
rational(U1, U2)(in U1 n, in U2 d) pure nothrow {
    return typeof(return)(n, d);
}
 
alias Rational = RationalT!BigInt;
 
version (arithmetic_rational_main) { // Test.
    void main() {
        import std.stdio, std.math;
        alias RatL = RationalT!long;
 
        foreach (immutable p; 2 .. 2 ^^ 19) {
            auto sum = RatL(1, p);
            immutable limit = 1 + cast(uint)real(p).sqrt;
            foreach (immutable factor; 2 .. limit)
                if (p % factor == 0)
                    sum += RatL(1, factor) + RatL(factor, p);
            if (sum.denominator == 1)
                writefln("Sum of recipr. factors of %6s = %s exactly%s",
                         p, sum, (sum == 1) ? ", perfect." : ".");
        }
    }
}