Last active
November 28, 2016 03:15
-
-
Save leontastic/8e42bbfb0d97b8f4f5dc to your computer and use it in GitHub Desktop.
Fraction: an ES6 class to preserve decimal precision in rational number arithmetic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Fraction: an ES6 class to preserve decimal precision in rational number arithmetic | |
Notes: | |
- reliably preserves decimal precision for floats that can be represented in decimal with 16 decimal places or less | |
- for decimals with more than 16 decimal places, precision is preserved up to 16 decimal places | |
CONSTRUCTOR: | |
constructor([a|Number|Fraction]) => [Fraction]: returns a Fraction from a number | |
STATIC METHODS: | |
gcd([a|Number], [b|Number]) => [Number] recursively finds greatest common denominator of a and b | |
lcm([a|Number], [b|Number]) => [Number] finds the lowest common multiple of a and b (depends on gcd(a,b)) | |
INSTANCE METHODS: | |
reduce() => [] reduces the fraction to simplest terms (used internally to always keep simplest terms) | |
add([n|Number|Fraction]) => [Fraction] returns fraction plus n | |
subtract([n|Number|Fraction]) => [Fraction] returns fraction minus n | |
multiply([n|Number|Fraction]) => [Fraction] returns fraction multiplied by n | |
divide([n|Number|Fraction]) => [Fraction] returns fraction divided by n | |
mod([n|Number|Fraction]) => [Fraction] returns fraction mod n | |
lt([n|Number|Fraction]) => [Boolean] returns true if fraction is less than n | |
gt([n|Number|Fraction]) => [Boolean] returns true if fraction is greater than n | |
toNumber() => [Number] returns the native Number representation of the fraction | |
*/ | |
class Fraction { | |
constructor(num) { | |
if (typeof num === 'number') { | |
this.numerator = num; | |
this.denominator = 1; | |
} else if (num instanceof Fraction) { | |
this.numerator = num.numerator; | |
this.denominator = num.denominator; | |
} else { | |
throw new Error('Invalid conversion to Fraction'); | |
} | |
// we always want the numerator to be an integer | |
// convert float to integer via string manipulation | |
// (since multiplication sometimes results in floating point errors) | |
if (this.numerator % 1 !== 0) { | |
let exp = (this.numerator + '').replace(/^-?\d*\.?|0+$/g, '').length; // count number of decimal places | |
this.numerator = parseInt((this.numerator + '').replace('.', '')); // strip decimal point and parse as integer | |
this.denominator = Math.pow(10, exp); | |
} | |
// new fractions should always be in lowest terms | |
this.reduce(); | |
} | |
// GCD utility (recursive algorithm) | |
static gcd(a, b) { | |
return b ? Fraction.gcd(b, a % b) : a; | |
} | |
// LCM utility (depends on Fraction.gcd) | |
static lcm(a, b) { | |
return Math.abs(a * b) / Fraction.gcd(a, b); | |
} | |
// reduces fraction to simplest terms | |
reduce() { | |
let d = Fraction.gcd(this.numerator, this.denominator); | |
this.numerator /= d; | |
this.denominator /= d; | |
} | |
// add a number or fraction to this fraction | |
add(n) { | |
if (!(n instanceof Fraction)) n = new Fraction(n); | |
let output = new Fraction(this); // copy this fraction | |
let multiple = Fraction.lcm(output.denominator, n.denominator); | |
output.numerator = output.numerator * (multiple / output.denominator) + n.numerator * (multiple / n.denominator); | |
output.denominator = multiple; | |
output.reduce(); | |
return output; | |
} | |
// subtract a number or fraction from this fraction | |
subtract(n) { | |
if (!(n instanceof Fraction)) n = new Fraction(n); | |
let output = new Fraction(this); // copy this fraction | |
let multiple = Fraction.lcm(output.denominator, n.denominator); | |
output.numerator = output.numerator * (multiple / output.denominator) - n.numerator * (multiple / n.denominator); | |
output.denominator = multiple; | |
output.reduce(); | |
return output; | |
} | |
// multiply this fraction by a number or fraction | |
multiply(n) { | |
if (!(n instanceof Fraction)) n = new Fraction(n); | |
let output = new Fraction(this); // copy this fraction | |
output.numerator *= n.numerator; | |
output.denominator *= n.denominator; | |
output.reduce(); | |
return output; | |
} | |
// divide this fraction by a number or fraction | |
divide(n) { | |
if (!(n instanceof Fraction)) n = new Fraction(n); | |
let output = new Fraction(this); // copy this fraction | |
output.numerator *= n.denominator; | |
output.denominator *= n.numerator; | |
output.reduce(); | |
return output; | |
} | |
// mod this fraction by a number or fraction (same as JS modulo operator '%') | |
mod(n) { | |
if (!(n instanceof Fraction)) n = new Fraction(n); | |
let output = new Fraction(this); // copy this fraction | |
if (output.gt(0)) { | |
while (output.gt(n)) { | |
output = output.subtract(n); | |
} | |
} else { | |
while (output.lt(n)) { | |
output = output.add(n); | |
} | |
} | |
return output.toNumber(); | |
} | |
// check if this fraction is less than a number or fraction | |
lt(n) { | |
if (!(n instanceof Fraction)) n = new Fraction(n); | |
let output = new Fraction(this); // copy this fraction | |
return output.toNumber() < n.toNumber(); | |
} | |
// check if this fraction is greater than a number or fraction | |
gt(n) { | |
if (!(n instanceof Fraction)) n = new Fraction(n); | |
let output = new Fraction(this); // copy this fraction | |
return output.toNumber() > n.toNumber(); | |
} | |
// get Number representation of fraction | |
toNumber() { | |
return this.numerator / this.denominator; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment