# Rational class supporting precise numbers

This is my JavaScript Rational class, similar to other languages' BigFraction class, but with one major difference:

This class doesn't support arbitrarily large rational numbers:

i.e. new Rational(Number.MAX_VALUE + 1) may fail.
However, it supports arbitrarily precise numbers
i.e. new Rational(1, 10).add(new Rational(2, 10)) will be exactly new Rational(3, 10) instead of .3000000000004.

window.Rational = (function() {
var GCF, LCM;

// Internal GCF and LCM

GCF = function(a, b) {
var temp;
if (a < 0) a *= -1;
if (b < 0) b *= -1;
if (a < b) {
temp = a;
a = b;
b = temp;
}
return a % b === 0 ? b : GCF(a % b, b);
};

LCM = function(a, b) {
return Math.abs(a * b) / GCF(a, b);
};

function Rational(top, bottom) {
this.top = top;
// bottom defaults to 1
this.bottom = bottom != null ? bottom : 1;
this.simplify();
}

var lcm = LCM(this.bottom, other.bottom);
// If we multiply by lcm
// it becomes an integer
// but we round to prevent floating point innacuracies
var top1 = Math.round(this * lcm);
var top2 = Math.round(other * lcm);
return new Rational(top1 + top2, lcm);
};

Rational.prototype.subtract = function(other) {
};

Rational.prototype.multiply = function(other) {
return new Rational(this.top * other.top, this.bottom * other.bottom);
};

Rational.prototype.divide = function(other) {
return this.multiply(other.reciprocal());
};

Rational.prototype.reciprocal = function() {
return new Rational(this.bottom, this.top);
};

Rational.prototype.isLessThan = function(other) {
return this < other;
};

Rational.prototype.isGreaterThan = function(other) {
return this > other;
};

Rational.prototype.isEqualTo = function(other) {
return this.top === other.top && this.bottom === other.bottom;
};

Rational.prototype.valueOf = function() {
return this.top / this.bottom;
};

Rational.prototype.simplify = function() {
var gcf;
if (this.top === 0) {
// No need to calculate GCF because GCF = 0
this.bottom = 1;
return;
}
if (this.bottom === 0) {
throw new Error("Cannot have denominator 0");
}
gcf = GCF(this.top, this.bottom);
if (this.top < 0 && this.bottom < 0) {
// Since GCF doesn't handle negatives
// if both numerator and denominators are negative
// we can cancel the negative
gcf *= -1;
}
// If the bottom is less than zero, then move the negative sign to the top
if (this.bottom < 0 && this.top >= 0) {
this.top *= -1;
this.bottom *= -1;
}
this.top /= gcf;
this.bottom /= gcf;
return this;
};

Rational.constants = {
ZERO: new Rational(0),
ONE: new Rational(1),
NEGATIVE_ONE: new Rational(-1)
};
return Rational;

})();


Questions:

• I think that by doing this * 5 or other * 6, I implicitly call valueOf(). Is that okay? Should I make it explicit (to me, saying this * 5 is clear: multiply this by 5), but I'm not sure in general.
• Same as the question above for this < other and this > other.
• Can I refactor my GCF and LCM algorithms to take into account negative numbers so that I don't have to handle them as special cases in the simplify function? Right now, they simply make everything positive and return a positive number.

Not a complete answer, but I noticed some bugs:

LCM = function(a, b) {
return Math.abs(a * b) / GCF(a, b);
};


The computation of a * b will easily overflow (thus losing precision) for large numbers a, b. You can improve matters by rewriting it as

function LCM(a, b) {
var g = a / GCF(a,b);
return Math.abs(g * b);
}


I also used function LCM as opposed to LCM = function just as a stylistic preference. See here.

Similarly:

Rational.prototype.add = function(other) {
var lcm = LCM(this.bottom, other.bottom);
// If we multiply by lcm
// it becomes an integer
// but we round to prevent floating point innacuracies
var top1 = Math.round(this * lcm);
var top2 = Math.round(other * lcm);
return new Rational(top1 + top2, lcm);
};


If I understand correctly, in the expression this * lcm, lcm is a Number, so you're basically doing this.valueOf() * lcm, which again wipes out all your pretended precision. You should avoid big numbers (which become floating-point), and you should definitely avoid unnecessary floating-point divisions.

Rational.prototype.add = function(other) {
var lcm = LCM(this.bottom, other.bottom);
var top1 = this.top * (lcm / this.bottom);
var top2 = other.top * (lcm / other.bottom);

return new Rational(top1 + top2, lcm);
};

• Your add function doesn't work. It should be lcm / this.bottom not this.bottom / lcm. But isn't that the same thing: this.top * (lcm / this.bottom) and (this.top / this.bottom) * lcm? Oct 15 '14 at 20:35
• Good catch; I'll fix it inline. But to your question: No! A concrete example is top=3, bottom=47, lcm=188. (3 * (188/47)) is correctly 12, but (3/47) * 188 is incorrectly 11.999999999999998. Try it for yourself! :) Remember, the whole point of your project is to use exact rational representations, so any time you have to drop into floating-point should be a huge red flag to you. Oct 15 '14 at 23:45