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


// the constructor
function Rational(numerator, denominator) {
    if (denominator === undefined)
        denominator = 1;
    else if (denominator == 0)
        throw "divide by zero";
 
    this.numer = numerator;
    if (this.numer == 0)
        this.denom = 1;
    else
        this.denom = denominator;
 
    this.normalize();
}
 
// getter methods
Rational.prototype.numerator   = function() {return this.numer};
Rational.prototype.denominator = function() {return this.denom};
 
// clone a rational
Rational.prototype.dup = function() {
    return new Rational(this.numerator(), this.denominator()); 
};
 
// conversion methods
Rational.prototype.toString = function() {
    if (this.denominator() == 1) {
        return this.numerator().toString();
    } else {
        // implicit conversion of numbers to strings
        return this.numerator() + '/' + this.denominator()
    }
};
Rational.prototype.toFloat  = function() {return eval(this.toString())}
Rational.prototype.toInt    = function() {return Math.floor(this.toFloat())};
 
// reduce 
Rational.prototype.normalize = function() {
    // greatest common divisor
    var a=Math.abs(this.numerator()), b=Math.abs(this.denominator())
    while (b != 0) {
        var tmp = a;
        a = b;
        b = tmp % b;
    }
    // a is the gcd
 
    this.numer /= a;
    this.denom /= a;
    if (this.denom < 0) {
        this.numer *= -1;
        this.denom *= -1;
    }
    return this;
}
 
// absolute value
// returns a new rational
Rational.prototype.abs = function() {
    return new Rational(Math.abs(this.numerator()), this.denominator());
};
 
// inverse
// returns a new rational
Rational.prototype.inv = function() {
    return new Rational(this.denominator(), this.numerator());
};
 
//
// arithmetic methods
 
// variadic, modifies receiver
Rational.prototype.add = function() {
    for (var i = 0; i < arguments.length; i++) {
        this.numer = this.numer * arguments[i].denominator() + this.denom * arguments[i].numerator();
        this.denom = this.denom * arguments[i].denominator();
    }
    return this.normalize();
};
 
// variadic, modifies receiver
Rational.prototype.subtract = function() {
    for (var i = 0; i < arguments.length; i++) {
        this.numer = this.numer * arguments[i].denominator() - this.denom * arguments[i].numerator();
        this.denom = this.denom * arguments[i].denominator();
    }
    return this.normalize();
};
 
// unary "-" operator
// returns a new rational
Rational.prototype.neg = function() {
    return (new Rational(0)).subtract(this);
};
 
// variadic, modifies receiver
Rational.prototype.multiply = function() {
    for (var i = 0; i < arguments.length; i++) {
        this.numer *= arguments[i].numerator();
        this.denom *= arguments[i].denominator();
    }
    return this.normalize();
};
 
// modifies receiver
Rational.prototype.divide = function(rat) {
    return this.multiply(rat.inv());
}
 
 
// increment
// modifies receiver
Rational.prototype.inc = function() {
    this.numer += this.denominator();
    return this.normalize();
}
 
// decrement
// modifies receiver
Rational.prototype.dec = function() {
    this.numer -= this.denominator();
    return this.normalize();
}
 
//
// comparison methods
 
Rational.prototype.isZero = function() {
    return (this.numerator() == 0);
}
Rational.prototype.isPositive = function() {
    return (this.numerator() > 0);
}
Rational.prototype.isNegative = function() {
    return (this.numerator() < 0);
}
 
Rational.prototype.eq = function(rat) {
    return this.dup().subtract(rat).isZero();
}
Rational.prototype.ne = function(rat) {
    return !(this.eq(rat));
}
Rational.prototype.lt = function(rat) {
    return this.dup().subtract(rat).isNegative();
}
Rational.prototype.gt = function(rat) {
    return this.dup().subtract(rat).isPositive();
}
Rational.prototype.le = function(rat) {
    return !(this.gt(rat));
}
Rational.prototype.ge = function(rat) {
    return !(this.lt(rat));
}