Arithmetic/Rational/JavaScript

From Rosetta Code
Arithmetic/Rational/JavaScript is part of Rational Arithmetic. You may find other members of Rational Arithmetic at Category:Rational Arithmetic.
The core of the Rational class
// 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));
}
Testing
function assert(cond, msg) { if (!cond) throw msg; }
 
print('testing')
var a, b, c, d, e, f;
 
//test creation
a = new Rational(0); assert(a.toString() == "0", "Rational(0).toString() == '0'")
a = new Rational(2); assert(a.toString() == "2", "Rational(2).toString() == '2'")
a = new Rational(1,2); assert(a.toString() == "1/2", "Rational(1,2).toString() == '1/2'")
b = new Rational(2,-12); assert(b.toString() == "-1/6", "Rational(1,6).toString() == '1/6'")
f = new Rational(0,9)
 
a = new Rational(1,3)
b = new Rational(1,2)
c = new Rational(1,3)
 
assert(!(a.eq(b)), "1/3 == 1/2")
assert(a.eq(c), "1/3 == 1/3")
assert(a.ne(b), "1/3 != 1/2")
assert(!(a.ne(c)), "1/3 != 1/3")
assert(a.lt(b), "1/3 < 1/2")
assert(!(b.lt(a)), "1/2 < 1/3")
assert(!(a.lt(c)), "1/3 < 1/3")
assert(!(a.gt(b)), "1/3 > 1/2")
assert(b.gt(a), "1/2 > 1/3")
assert(!(a.gt(c)), "1/3 > 1/3")
 
assert(a.le(b), "1/3 <= 1/2")
assert(!(b.le(a)), "1/2 <= 1/3")
assert(a.le(c), "1/3 <= 1/3")
assert(!(a.ge(b)), "1/3 >= 1/2")
assert(b.ge(a), "1/2 >= 1/3")
assert(a.ge(c), "1/3 >= 1/3")
 
a = new Rational(1,2)
b = new Rational(1,6)
a.add(b); assert(a.eq(new Rational(2,3)), "1/2 + 1/6 == 2/3")
c = a.neg(); assert(a.eq(new Rational(2,3)), "neg(1/2) == -1/2")
assert(c.eq(new Rational(2,-3)), "neg(1/2) == -1/2")
d = c.abs(); assert(c.eq(new Rational(-2,3)), "abs(neg(1/2)) == 1/2")
assert(d.eq(new Rational(2,3)), "abs(neg(1/2)) == 1/2")
b.subtract(a); assert(b.eq(new Rational(-1,2)), "1/6 - 1/2 == -1/3")
 
c = a.neg().abs(); assert(c.eq(a), "abs(neg(1/2)) == 1/2")
c = (new Rational(-1,3)).inv(); assert(c.toString() == '-3', "inv(1/6 - 1/2) == -3")
try {
e = f.inv();
throw "should have been an error: " +f + '.inv() = ' + e
} catch (e) {
assert(e == "divide by zero", "0.inv() === error")
}
 
b = new Rational(1,6)
b.add(new Rational(2,3), new Rational(4,2)); assert(b.toString() == "17/6", "1/6+2/3+4/2 == 17/6");
 
a = new Rational(1,3);
b = new Rational(1,6)
c = new Rational(5,6);
d = new Rational(1/5);
e = new Rational(2);
f = new Rational(0,9);
 
 
assert(c.dup().multiply(d).eq(b), "5/6 * 1/5 = 1/6")
assert(c.dup().multiply(d,e).eq(a), "5/6 * 1/5 *2 = 1/3")
assert(c.dup().multiply(d,e,f).eq(f), "5/6 * 1/5 *2*0 = 0")
 
c.divide(new Rational(5));
assert(c.eq(b), "5/6 / 5 = 1/6b")
 
try {
e = c.divide(f)
throw "should have been an error: " + c + "/" + f + '= ' + e
} catch (e) {
assert(e == "divide by zero", "0.inv() === error")
}
 
 
print('all tests passed');
Finding perfect numbers
function factors(num) {
var factors = new Array();
var sqrt = Math.floor(Math.sqrt(num));
for (var i = 1; i <= sqrt; i++) {
if (num % i == 0) {
factors.push(i);
if (num / i != i)
factors.push(num / i);
}
}
factors.sort(function(a,b){return a-b}); // numeric sort
return factors;
}
 
function isPerfect(n) {
var sum = new Rational(0);
var fctrs = factors(n);
for (var i = 0; i < fctrs.length; i++)
sum.add(new Rational(1, fctrs[i]));
 
// note, fctrs includes 1, so sum should be 2
return sum.toFloat() == 2.0;
}
 
// find perfect numbers less than 2^19
for (var n = 2; n < Math.pow(2,19); n++)
if (isPerfect(n))
print("perfect: " + n);
 
// test 5th perfect number
var n = Math.pow(2,12) * (Math.pow(2,13) - 1);
if (isPerfect(n))
print("perfect: " + n);
Output:
perfect: 6
perfect: 28
perfect: 496
perfect: 8128
perfect: 33550336