UNPKG

lb-ratio

Version:

Provides a Fraction, Rational, and Ratio object for javascript.

985 lines (972 loc) 33.7 kB
/** * Ratio.js, provides a Ratio(Fraction) object for Javascript. * Created by Larry Battle, https://github.com/LarryBattle * license MIT, http://www.opensource.org/licenses/mit-license * Project Page: https://github.com/LarryBattle/Ratio.js * * @module Ratio **/ var Ratio = (function() { "use strict"; /** * Ratio is an object that has a numerator and denominator, corresponding to a/b.<br/> * Note that the keyword `new` is not required to create a new instance of the Ratio object, since this is done for you.<br/> * In otherwords, `new Ratio( value )` is the same as `Ratio( value )`. * * @class Ratio * @constructor * @chainable * @param {Ratio|String|Number} [numerator=0] can be a Ratio object or numeric value. * @param {Ratio|String|Number} [denominator=1] can be a Ratio object or numeric value. * @param {Boolean} [alwaysReduce] if true, then the Ratio object and the children from it will always represent the simplified form of the rational. * @return {Ratio} object that has a numerator and denominator, corresponding to a/b. * @example Ratio(2,4).toString() === "2/4"; Ratio("2/4").toString() === "NaN/1" // Use Ratio.parse()! **/ var Ratio = function(numerator, denominator, alwaysReduce) { if (!(this instanceof Ratio)) { return new Ratio(numerator, denominator, alwaysReduce); } this.divSign = "/"; this.alwaysReduce = !!alwaysReduce; var arr = Ratio.getStandardRatioArray(numerator, denominator, this.alwaysReduce); this._n = arr[0]; this._d = arr[1]; return this; }; /** * Represents the maximum amount of precision avaiable. <br/> * Any value with more digits will become estimations. * * @property Ratio.MAX_PRECISION * @type {Number} */ Ratio.MAX_PRECISION = (1 / 3).toString().length - 2; /** * Represents the largest value that stored without loss of precision. <br/> * Any value larger will become estimations. * Source: "http://stackoverflow.com/questions/307179/what-is-javascripts-max-int-whats-the-highest-integer-value-a-number-can-go-t" * * @property Ratio.MAX_VALUE * @type {Number} */ Ratio.MAX_VALUE = Math.pow(2, 53); /** * Represents the smallest value that stored without loss of precision. <br/> * Any value smaller will become estimations. * Source: "http://stackoverflow.com/questions/307179/what-is-javascripts-max-int-whats-the-highest-integer-value-a-number-can-go-t" * * @property Ratio.MIN_VALUE * @type {Number} */ Ratio.MIN_VALUE = -Math.pow(2, 53); /** * Stores complex regular expressions. * * @property Ratio.regex * @type {Object} */ Ratio.regex = { divSignCheck: /(\d|Infinity)\s*\//, divSignSplit: /\//, cleanFormat: /^\d+\.\d+$/, mixedNumbers: /(\S+)\s+(\S[\w\W]*)/, repeatingDecimals: /[^\.]+\.\d*(\d{2,})+(?:\1)$/, repeatingNumbers: /^(\d+)(?:\1)$/ }; /** * Version number of Ratio.js * * @property Ratio.VERSION * @type String **/ Ratio.VERSION = "<%= version %>"; /** * Checks if value is a finite number. <br/> Borrowed from jQuery 1.7.2 <br/> * * @method Ratio.isNumeric * @param {Object} obj * @return {Boolean} * @example Ratio.isNumeric("1.0e3") === true **/ Ratio.isNumeric = function(obj) { return !isNaN(parseFloat(obj)) && isFinite(obj); }; /** * Returns the default value if the provided new value is undefined or null. * * @method Ratio.getValueIfDefined * @param {Object} backup - default value * @param {Object} value * @return {Object} * @example Ratio.getValueIfDefined( 4, null ) === 4 **/ Ratio.getValueIfDefined = function(backup, value) { return typeof value !== "undefined" && value !== null ? value : backup; }; /** * Find the Greatest Common Factor between two numbers using the Euler Method. * Will return the first argument if only one argument is passed. * * @method Ratio.gcd * @param {Number} a * @param {Number} b * @return {Number} * @example Ratio.gcd(20,12) === 4 **/ Ratio.gcd = function(a, b) { if (arguments.length < 2) { return a; } var c; a = +a; b = +b; // Same as isNaN() but faster if (a !== a || b !== b) { return NaN; } //Same as !isFinite() but faster if (a === Infinity || a === -Infinity || b === Infinity || b === -Infinity) { return Infinity; } // Checks if a or b are decimals if ((a % 1 !== 0) || (b % 1 !== 0)) { throw new Error("Can only operate on integers"); } while (b) { c = a % b; a = b; b = c; } return (0 < a) ? a : -a; }; /** * Returns the numerator with the corresponding sign of (top/bottom).<br/> * * @method Ratio.getNumeratorWithSign * @param {Number} top * @param {Number} bottom * @return {Number} * @example Ratio.getNumeratorWithSign(1,-2) === -1 **/ Ratio.getNumeratorWithSign = function(top, bottom) { var sign = (+top * (+bottom || 1)) < 0 ? -1 : 1; return Math.abs(+top) * sign; }; /** * Provides a quick way to find out the numeric type of an object. * Types include: `NaN`, `Ratio`, `number`, `e`, `decimal`, `mixed` and `fraction` * * @method Ratio.guessType * @param {*} obj * @return {String} type * @example Ratio.guessType("1/3") === "fraction"; **/ Ratio.guessType = function(obj) { var type = "NaN"; if (obj instanceof Ratio) { type = "Ratio"; } else if (!isNaN(obj)) { type = "number"; if (-1 < (+obj).toString().indexOf("e")) { type = "e"; } else if (obj % 1) { type = "decimal"; } } else if (Ratio.regex.divSignCheck.test(obj)) { if (/\d\s+[+\-]?\d/.test(obj)) { type = "mixed"; } else { type = "fraction"; } } return type; }; /** * Converts a numeric value to an array in the form of [top, bottom], such that top/bottom evaluates to the passed value. * * @method Ratio.parseToArray * @param {Number|String} obj Numeric Object. * @return {Array[Number, Number]} * @example Ratio.parseToArray( 0.125 ) // returns [125, 1000] **/ Ratio.parseToArray = function(obj) { var parts = [], sign, index, arr = [], top; switch (Ratio.guessType(obj)) { case "mixed": parts = obj.match(Ratio.regex.mixedNumbers); arr = Ratio.parseToArray(parts[2]); sign = (+parts[1] < 0 || +arr[0] < 0) ? -1 : 1; arr[0] = sign * (Math.abs(arr[0]) + Math.abs(parts[1] * arr[1])); break; case "fraction": parts = obj.split(Ratio.regex.divSignSplit); arr[0] = Ratio.getNumeratorWithSign(parts[0], parts[1]); arr[1] = Math.abs(+parts[1]); break; case "decimal": parts = (+obj).toString().split("."); arr[1] = Math.pow(10, parts[1].length); arr[0] = Math.abs(parts[0]) * arr[1] + (+parts[1]); arr[0] = (-1 < (parts[0]).indexOf("-")) ? -arr[0] : arr[0]; break; case "number": arr = [+obj, 1]; break; case "e": parts = (+obj).toString().split(/e/i); top = Ratio.parseToArray(parts[0]); index = (Math.abs(+obj) < 1) ? [0, 1] : [1, 0]; arr[index[0]] = top[index[0]]; arr[index[1]] = Number(top[index[1]] + "e" + Math.abs(+parts[1])); break; case "Ratio": arr = [obj._n, obj._d]; break; default: arr = [NaN, 1]; } return arr; }; /** * Converts a numeric value to a Ratio object. * Supports mixed numbers, whole numbers, decimals, scientific numbers and Ratio objects. * * @method Ratio.parse * @chainable * @param {Ratio|Number|String} obj - numerator * @param {Ratio|Number|String} [obj] - denominator * @return {Ratio} * @example Ratio.parse(22,7).toString() === "22/7"; //whole numbers Ratio.parse("3 1/7").toString() === "22/7"; // mixed numbers Ratio.parse(22/7).simplify().toLocaleString() === "3 1/7"; // decimals Ratio.parse("22/7").toLocaleString() === "3 1/7"; // fractions Ratio.parse("22e31/70e30").simplify().toLocaleString() === "3 1/7"; // scientific notated numbers **/ Ratio.parse = function(obj, obj2) { var arr = Ratio.parseToArray(obj), arr2; if (arr.length && obj2 !== undefined && obj2 !== null) { arr2 = Ratio.parseToArray(obj2); arr[0] *= arr2[1]; arr[1] *= arr2[0]; } return new Ratio(arr[0], arr[1]); }; /** * Returns an array of two numbers that represent ratio of the passed values. * * @method Ratio.simplify * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj] * @return {Array[ Number, Number ]} * @example Example 1: Ratio.simplify( Ratio(36,-36) ); // returns [-1,1] Example 2: Ratio.simplify( "9/12" ); // returns [3,4] Example 3: Ratio.simplify( "10/4", "5/3" ); // returns [3,2] because ("10/4", "5/3") => ("6/4") Ratio.simplify( "6/4" ); // returns [3,2] **/ Ratio.simplify = function(obj, obj2) { obj = Ratio.parse(obj, obj2); var top = obj._n, bottom = top || !obj._d ? obj._d : 1, arr = Ratio.getRepeatProps(top / bottom), factor; if (arr.length) { top = Number(arr.join('')) - Number(arr[0] + String(arr[1])); bottom = Math.pow(10, arr[1].length) * (Math.pow(10, arr[2].length) - 1); } factor = Ratio.gcd(top, bottom); return [top / factor, bottom / factor]; }; /** * This function divides a repeating decimal into 3 parts. If the value passed is not a repeating decimal then an empty array is returned.<br/> * For repeating decimals, the return value is an array which contains the numeric value split into 3 parts like, <br/> * [ "numbers before decimal", "numbers before repeating pattern", "repeating pattern." ].<br/> * Here's another explanation. <br/> * The return value is [i, x, r] for the repeating decimal value.<br/> * where i are the values to the left of the decimal point. <br/> * x are the decimals to the right of the decimal point and to the left of the repeating pattern.<br/> * r is the unique repeating patterns for the repeating decimal.<br/> * Example. 22/7 = 3.142857142857143 = 3.14-285714-285714-3, i = 3, x = 14, r = 285714<br/> * It should be noted that the last digit might be removed to avoid rounding errors. * * @method Ratio.getRepeatProps * @param {Number} val * @return {Array[String, String, String]} - Must return strings because of zeros in pattern. * @example Ratio.getRepeatProps( 22/7 ) // returns ["3", "14", "285714"] **/ Ratio.getRepeatProps = function(val) { val = String(val || ""); var arr = [], match = Ratio.regex.repeatingDecimals.exec(val), RE2_RE1AtEnd, RE3_RepeatingNums = Ratio.regex.repeatingNumbers; if (!match) { val = val.replace(/\d$/, ""); match = Ratio.regex.repeatingDecimals.exec(val); } if (match && 1 < match.length && /\.\d{10}/.test(match[0])) { match[1] = RE3_RepeatingNums.test(match[1]) ? RE3_RepeatingNums.exec(match[1])[1] : match[1]; RE2_RE1AtEnd = new RegExp("(" + match[1] + ")+$"); arr = val.split(/\./).concat(match[1]); arr[1] = arr[1].replace(RE2_RE1AtEnd, ""); } return arr; }; /** * Returns the prime factors of a number. <br/> * More info <http://bateru.com/news/2012/05/code-of-the-day-javascript-prime-factors-of-a-number/> * * @method Ratio.getPrimeFactors * @param {Number} num * @return {Array} an array of numbers * @example Ratio.getPrimeFactors(20).join(',') === "2,2,5" **/ Ratio.getPrimeFactors = function(num) { num = Math.floor(num); var root, factors = [], x, sqrt = Math.sqrt, doLoop = 1 < num && isFinite(num); while (doLoop) { root = sqrt(num); x = 2; if (num % x) { x = 3; while ((num % x) && ((x += 2) < root)) {} } x = (root < x) ? num : x; factors.push(x); doLoop = (x !== num); num /= x; } return factors; }; /** * Rounds up a scientific notated number with 8+ trailing 0s or 9s.<br/> * * @method Ratio.getCleanENotation * @param {Number} num * @return {String} - Returns number as string to preserve value. * @example Example 1 Ratio.getCleanENotation( "1.1000000000000003e-30" ) === "1.1e-30"; Example 2 Ratio.getCleanENotation( "9.999999999999999e+22" ) === "1e+23"; **/ Ratio.getCleanENotation = function(num) { num = (+num || 0).toString(); if (/\.\d+(0|9){8,}\d?e/.test(num)) { var i = num.match(/(?:\d+\.)(\d+)(?:e[\w\W]*)/)[1].replace(/(0|9)+\d$/, '').length + 1; num = (+num).toPrecision(i).toString(); } return num; }; /** * Moves all the zeros for scientific numbers to the first or second element.</br> * The most takes all. This helps simplify computational errors with `SN`s. *Need to reword* * * @param {Number} top * @param {Number} bottom * @return {Array} - Pair of numbers * @method Ratio.simplifyENotation * @example Ratio.simplifyENotation(3e80,3e35); // returns [3e45,3] */ Ratio.simplifyENotation = function(top, bottom) { var val = top / bottom, re = /[eE]/; if (!isNaN(val) && re.test(top) && re.test(bottom)) { var arr = (top).toString().split("e"), arr2 = (bottom).toString().split("e"); if (Number(arr2[1]) < Number(arr[1])) { arr[1] = Number(arr[1]) + (-1 * arr2[1]); arr2[1] = 0; } else { arr2[1] = Number(arr2[1]) + (-1 * arr[1]); arr[1] = 0; } top = Number(arr.join("e")); bottom = Number(arr2.join("e")); } return [top, bottom]; }; /** * Used to combine two ratios into one. * * @method Ratio.getCombinedRatio * @chainable * @param {Ratio|String|Number} [obj] * @param {Ratio|String|Number} [obj] * @return {Ratio} [obj] * @example Ratio.getCombinedRatio("1/2","1/3").toString() === "3/2" **/ Ratio.getCombinedRatio = function(obj, obj2) { if (!(obj instanceof Ratio) || obj2 !== undefined) { obj = Ratio.parse(obj, obj2); } return obj; }; /** * Returns a new Ratio with random values for the numerator and denominator. * Values range from [0, 1] * * @method Ratio.random * @chainable * @return {Ratio} * @example Ratio.random().toString(); // might return "1/4" */ Ratio.random = function() { var value = (Math.random()).toFixed(Math.floor(Math.random() * 16)); return Ratio.parse(value).simplify(); }; /** * Returns an ratio as an array with the first element containing the sign of the fraction. * @method Ratio.getStandardRatioArray() * * @param {Number} a - numerator * @param {Number} b - denominator * @param {Boolean} alwaysReduce - if true, then returns the simplify fraction. * @return {Array} [numerator, denominator] * @example Ratio.getStandardRatioArray(-10,-20, true); // returns [1,2] * */ Ratio.getStandardRatioArray = function(a, b, alwaysReduce) { if (typeof b === "undefined") { b = 1; if (typeof a === "undefined") { a = 0; } } var denominator = +Math.abs(b); var numerator = Ratio.getNumeratorWithSign(a, (b || 1)); var arr = [numerator, denominator]; if (arr[1] && alwaysReduce) { arr = Ratio.simplify(arr[0], arr[1]); } return arr; }; Ratio.prototype = { constructor: Ratio, /** * A setter and getter for the numerator * @method Ratio.prototype.numerator * @param {Ratio|String|Number} val - numerator * @return {Number} - numerator */ numerator: function(val) { if (typeof val !== "undefined") { this._n = Ratio.parse(val).valueOf(); } return this._n; }, /** * A setter and getter for the denominator * @method Ratio.prototype.denominator * @param {Ratio|String|Number} val - denominator * @return {Number} - denominator */ denominator: function(val) { if (typeof val !== "undefined") { this._d = Ratio.parse(val).valueOf(); this.correctRatio(); } return this._d; }, /** * For each ratio instance, corrects three main problems: * 1) Sets the numerator and denominator to default values if undefined. (Default fraction: 0/1) * 2) Places the sign on numerator. * 3) Reduces the ratio if needed. * * @method Ratio.prototype.correctRatio * @return {Ratio} * @example Ratio().toString() === "0/1"; // `.correctRatio()` was called internally. **/ correctRatio: function() { var arr = Ratio.getStandardRatioArray(this._n, this._d, this.alwaysReduce); this._n = arr[0]; this._d = arr[1]; return this; }, /** * From the Ratio instance, returns the raw values of the numerator and denominator in the form [numerator, denominator]. * * @method Ratio.prototype.toArray * @return {Array} an array of 2 numbers. * @example Ratio(1,2).toArray().join(',') === "1,2" **/ toArray: function() { return [this._n, this._d]; }, /** * From the Ratio instance, returns the result of the numerator divided by the denominator. * * @method Ratio.prototype.valueOf * @return {Number} * @example Example 1: Ratio(1,2).valueOf() === 0.5; **/ valueOf: function() { var arr = Ratio.simplifyENotation(this._n, this._d); return arr[0] / arr[1]; }, /** * From the Ratio instance, returns a string of the Ratio in fraction form if the numerator and denominator are Rational numbers.<br/> * The output format can be a whole number, mixed number, NaN, proper fraction depending on the computed value of (numerator / denominator). * * @method Ratio.prototype.toLocaleString * @return {String} * @example Example 1: Ratio(1,10).toLocaleString() === "1/10" Example 2: Ratio(0,0).toLocaleString() === "NaN" **/ toLocaleString: function() { var val = this.valueOf(), x, str; if (isNaN(val)) { str = "NaN"; } else if (val % 1 === 0 || this._d === 1 || !isFinite(val % 1)) { str = String(val); } else if (1 < Math.abs(val)) { x = parseInt(val, 10); str = x + " " + Math.abs(this._n % this._d) + String(this.divSign) + this._d; } else { str = this._n + String(this.divSign) + this._d; } return str; }, /** * From the Ratio instance, returns the raw values of the numerator and denominator in the form "a/b".<br/> * Note: The division symbol can be change by modification of the `divSign` property. * * @method Ratio.prototype.toString * @return {String} * @example Example 1: Ratio(8,2).toString() === "8/2"; Example 2: var a = Ratio(8,2); a.divSign = ":"; a.toString() == "8:2"; **/ toString: function() { return String(this._n + this.divSign + this._d); }, /** * Returns a new instance of the current Ratio.<br/> * The clone propery value can be changed if the appropriate argument value is supplied. * * @method Ratio.prototype.clone * @param {Number} [top] * @param {Number} [bottom] * @param {Boolean} [alwaysReduce] * @return {Ratio} * @example var a = Ratio(2,4); var b = a.clone(); a.equals(b) === true; **/ clone: function(top, bottom, alwaysReduce) { var func = Ratio.getValueIfDefined; top = func(this._n, top); bottom = func(this._d, bottom); alwaysReduce = func(this.alwaysReduce, alwaysReduce); return new Ratio(top, bottom, alwaysReduce); }, /** * Determines if a current instance value is not a number. * * @method Ratio.prototype.isNaN * @return {Boolean} * @example Ratio(1,2).isNaN() === false; */ isNaN: function() { return !Ratio.isNumeric(this.valueOf()); }, /** * Returns a simplifyd ratio from the current instance. * * @method Ratio.prototype.simplify * @chainable * @return {Ratio} * @example Ratio(10,2).simplify().toString() === "5/1" **/ simplify: function() { var arr = Ratio.simplify(this._n, this._d); return this.clone(arr[0], arr[1]); }, /** * Adds the current Ratio to another Ratio. * * @method Ratio.prototype.add * @chainable * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj2] * @return {Ratio} * @example Ratio( 1, 3 ).add( 1,2 ).toString() === "5/6" **/ add: function(obj, obj2) { obj = Ratio.getCombinedRatio(obj, obj2); var x, top, bottom; if (this._d === obj._d) { top = this._n + obj._n; bottom = this._d; } else { x = Ratio.gcd(this._d, obj._d); top = ((this._n * obj._d) + (this._d * obj._n)) / x; bottom = (this._d * obj._d) / x; } return this.clone(top, bottom); }, /** * Divides the current Ratio by another Ratio. * * @method Ratio.prototype.divide * @chainable * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj2] * @return {Ratio} * @example Ratio( 1,2 ).divide( 3,4 ).toString() === "4/6" **/ divide: function(obj, obj2) { obj = Ratio.getCombinedRatio(obj, obj2); return this.clone(this._n * obj._d, this._d * obj._n); }, /** * Returns if the current Ratio and another object have the same numeric value. * * @method Ratio.prototype.equals * @param {Object} obj * @return {Boolean} * @example Ratio(1,2).equals( 1/2 ) === true **/ equals: function(obj) { var val = (Ratio.isNumeric(obj) || obj instanceof Ratio) ? obj.valueOf() : Ratio.parse(obj).valueOf(); return (this._n / this._d) === +val; }, /** * Performs a strict comparison to determine if the current instances and passed object are identical. * * @method Ratio.prototype.deepEquals * @param {Object} obj * @return {Boolean} * @example Ratio(1,2).deepEquals( 1/2 ) === false */ deepEquals: function(obj) { return (obj instanceof Ratio) && (this._n === obj._n) && (this._d === obj._d) && (this.divSign === obj.divSign) && (this.alwaysReduce === obj.alwaysReduce); }, /** * Multiply the current Ratio by another Ratio. * * @chainable * @method Ratio.prototype.multiply * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj2] * @return {Ratio} * @example Ratio(2,5).multiply( 1, 2 ).toString() === "2/10" **/ multiply: function(obj, obj2) { obj = Ratio.getCombinedRatio(obj, obj2); return this.clone(this._n * obj._n, this._d * obj._d); }, /** * Subtracts the current Ratio from another Ratio. * * @method Ratio.prototype.subtract * @chainable * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj2] * @return {Ratio} * @example Ratio(2,3).subtract(1,7).toString() === "11/21" **/ subtract: function(obj, obj2) { obj = Ratio.getCombinedRatio(obj, obj2); var x, top, bottom; if (this._d === obj._d) { top = this._n - obj._n; bottom = this._d; } else { x = Ratio.gcd(this._d, obj._d); top = ((this._n * obj._d) - (this._d * obj._n)) / x; bottom = (this._d * obj._d) / x; } return this.clone(top, bottom); }, /** * Returns an new Ratio scaled down by a factor from the current instance. * * @method Ratio.prototype.descale * @chainable * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj2] * @return {Ratio} * @example Ratio(10,4).descale( 2 ).toString() === "5/2" **/ descale: function(obj, obj2) { var factor = Ratio.getCombinedRatio(obj, obj2); return this.clone(this._n / factor, this._d / factor); }, /** * From the Ratio instance, returns an new Ratio raised to a power. * * @method Ratio.prototype.pow * @chainable * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj2] * @return {Ratio} * @example Ratio(2,4).pow(4).toString() === "16/256" **/ pow: function(obj, obj2) { var power = Ratio.getCombinedRatio(obj, obj2); return this.clone(Math.pow(this._n, +power), Math.pow(this._d, +power)); }, /** * From the Ratio instance, returns a new Ratio scaled up by a factor. * * @method Ratio.prototype.scale * @chainable * @param {Ratio|Number|String} obj * @param {Ratio|Number|String} [obj2] * @return {Ratio} * @example Ratio(1,10).scale(10).toString() === "10/100" **/ scale: function(obj, obj2) { var factor = Ratio.getCombinedRatio(obj, obj2); return this.clone(this._n * +factor, this._d * +factor); }, /** * From the Ratio instance, returns a new Ratio by parsing the numerator and denominator.<br/> * This is useful if want to ensure that the Ratio contains only whole numbers in the numerator and denominator after a caclulation. * * @method Ratio.prototype.cleanFormat * @chainable * @return {Ratio} * @example var a = Ratio(20,30).descale(3); a.toString() == "6.666666666666667/10"; a.cleanFormat().toString() == "6666666666666667/10000000000000000" **/ cleanFormat: function() { var re = Ratio.regex.cleanFormat, obj; if (re.test(this._n) || re.test(this._d)) { return Ratio.parse(this._n, this._d); } obj = this.clone(); obj._n = Ratio.getCleanENotation(obj._n); obj._d = Ratio.getCleanENotation(obj._d); return obj; }, /** * Returns a new instances that is the absolute value of the current Ratio. * * @method Ratio.prototype.abs * @chainable * @return {Ratio} * @example Ratio(-3,2).abs().toString() === "3/2" **/ abs: function() { return this.clone(Math.abs(this._n)); }, /** * From the Ratio instance, returns a new Ratio in the form of (numerator mod denominator)/1.<br/> * Which is the same as Ratio( (numerator % denominator), 1 ). * * @method Ratio.prototype.mod * @chainable * @return {Ratio} * @example Ratio(3,10).mod().toString() === "3/1" **/ mod: function() { return this.clone(this._n % this._d, 1); }, /** * Returns a new instance of the Ratio with the sign toggled. * * @method Ratio.prototype.negate * @chainable * @return {Ratio} * @example Ratio(1,2).negate().toString() === "-1/2" **/ negate: function() { return this.clone(-this._n); }, /** * Determines if the current Ratio is a proper fraction. * * @method Ratio.prototype.isProper * @return {Boolean} * @example Ratio(12,3).isProper() == false; **/ isProper: function() { return Math.abs(this._n) < this._d; }, /** * Determines the value of x. Solves the following equations.<br/> * 1. `( a/b = x/n )` or * 2. `( a/b = n/x )` <br/> * Where a, b are the numerator and denominator respectively of the current Ratio.<br/> * Note: Returns null if the the string can't be split into exactly 2 elements. * * @method Ratio.prototype.findX * @chainable * @param {String} str a string representing a fraction with a 'x' in the numerator or denominator. * @return {Ratio} * @example Ratio(1,4).findX("x/20") == 5; **/ findX: function(str) { var arr = String(str).split("/"); if (arr.length !== 2 || (!isNaN(arr[0]) && !isNaN(arr[1]))) { return null; } return (isNaN(arr[0]) ? new Ratio(arr[1]).multiply(this) : new Ratio(arr[0]).divide(this)); }, /** * Switches the numerator and denominator positions. * * @method Ratio.prototype.reciprocal * @chainable * @return {Ratio} * @example Ratio(1,2).reciprocal().toString() == "2/1"; **/ reciprocal: function() { return this.clone(this._d, this._n); }, /** * From the Ratio instance, approxiates the value to a new fraction with a provided denominator. * In otherwords, this method helps you find out what fraction with a given denominator will best * represent the current numeric value of the Ratio. * Operates on a arbitary amount of arguments and returns the Ratio with the closest match among the quantities. * Therefore, an approximated quantity is returned if the absolute value of the difference between the approximated quantity and actual value is * smaller than the error rate. * * @method Ratio.prototype.toQuantityOf * @chainable * @param {Number, ...} base * @return {Ratio} * @example Ratio(27,100).toQuantityOf(3).toString() == "1/3"; Ratio(1,2).toQuantityOf(2,3,4).toString() === "1/2"; **/ toQuantityOf: function() { var val = this.valueOf(), x, diff, i, prevDiff = Infinity, len = arguments.length; for (i = 0; i < len; i += 1) { diff = Math.abs((Math.round(val * arguments[i]) / arguments[i]) - val); if (diff < prevDiff) { x = arguments[i]; prevDiff = diff; } } return this.clone(Math.round(val * x), x); }, /** * Returns a new Ratio from the floor of the current Ratio instance. * * @method Ratio.prototype.floor * @chainable * @return {Ratio} * @example Ratio.parse(4.2).floor().toString() === "4/1" */ floor: function() { return this.clone(Math.floor(this.valueOf()), 1); }, /** * Returns a new Ratio from the ceil of the current Ratio instance. * * @method Ratio.prototype.ceil * @chainable * @return {Ratio} * @example Ratio.parse(4.2).ceil().toString() === "5/1" */ ceil: function() { return this.clone(Math.ceil(this.valueOf()), 1); }, /** * Returns a new Ratio by removing the integer part of the current instance. * In otherwords, returns the decimal portion as a fraction. * * @method Ratio.prototype.makeProper * @chainable * @return {Ratio} * @example Ratio.parse(4.2).makeProper().toString() === "2/10" */ makeProper: function() { return this.clone(this._n % this._d, this._d); } /** * Use Ratio.prototype.simplify() instead. * @deprecated * @method Ratio.prototype.reduce */ }; return Ratio; } ()); // Adds npm support if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = Ratio; } exports.Ratio = Ratio; } else { this.Ratio = Ratio; }