1/* 2fraction.js 3A Javascript fraction library. 4 5Copyright (c) 2009 Erik Garrison <[email protected]> 6 7Permission is hereby granted, free of charge, to any person obtaining a copy 8of this software and associated documentation files (the "Software"), to deal 9in the Software without restriction, including without limitation the rights 10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11copies of the Software, and to permit persons to whom the Software is 12furnished to do so, subject to the following conditions: 13 14The above copyright notice and this permission notice shall be included in 15all copies or substantial portions of the Software. 16 17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23THE SOFTWARE. 24 25*/ 26 27 28/* Fractions */ 29/* 30 * 31 * Fraction objects are comprised of a numerator and a denomenator. These 32 * values can be accessed at fraction.numerator and fraction.denomenator. 33 * 34 * Fractions are always returned and stored in lowest-form normalized format. 35 * This is accomplished via Fraction.normalize. 36 * 37 * The following mathematical operations on fractions are supported: 38 * 39 * Fraction.equals 40 * Fraction.add 41 * Fraction.subtract 42 * Fraction.multiply 43 * Fraction.divide 44 * 45 * These operations accept both numbers and fraction objects. (Best results 46 * are guaranteed when the input is a fraction object.) They all return a new 47 * Fraction object. 48 * 49 * Usage: 50 * 51 * TODO 52 * 53 */ 54 55/* 56 * The Fraction constructor takes one of: 57 * an explicit numerator (integer) and denominator (integer), 58 * a string representation of the fraction (string), 59 * or a floating-point number (float) 60 * 61 * These initialization methods are provided for convenience. Because of 62 * rounding issues the best results will be given when the fraction is 63 * constructed from an explicit integer numerator and denomenator, and not a 64 * decimal number. 65 * 66 * 67 * e.g. new Fraction(1, 2) --> 1/2 68 * new Fraction('1/2') --> 1/2 69 * new Fraction('2 3/4') --> 11/4 (prints as 2 3/4) 70 * 71 */ 72Fraction = function(numerator, denominator) 73{ 74 /* double argument invocation */ 75 if (typeof numerator !== 'undefined' && denominator) { 76 if (typeof(numerator) === 'number' && typeof(denominator) === 'number') { 77 this.numerator = numerator; 78 this.denominator = denominator; 79 } else if (typeof(numerator) === 'string' && typeof(denominator) === 'string') { 80 // what are they? 81 // hmm.... 82 // assume they are ints? 83 this.numerator = parseInt(numerator); 84 this.denominator = parseInt(denominator); 85 } 86 /* single-argument invocation */ 87 } else if (typeof denominator === 'undefined') { 88 num = numerator; // swap variable names for legibility 89 if (typeof(num) === 'number') { // just a straight number init 90 this.numerator = num; 91 this.denominator = 1; 92 } else if (typeof(num) === 'string') { 93 var a, b; // hold the first and second part of the fraction, e.g. a = '1' and b = '2/3' in 1 2/3 94 // or a = '2/3' and b = undefined if we are just passed a single-part number 95 var arr = num.split(' ') 96 if (arr[0]) a = arr[0] 97 if (arr[1]) b = arr[1] 98 /* compound fraction e.g. 'A B/C' */ 99 // if a is an integer ... 100 if (a % 1 === 0 && b && b.match('/')) { 101 return (new Fraction(a)).add(new Fraction(b)); 102 } else if (a && !b) { 103 /* simple fraction e.g. 'A/B' */ 104 if (typeof(a) === 'string' && a.match('/')) { 105 // it's not a whole number... it's actually a fraction without a whole part written 106 var f = a.split('/'); 107 this.numerator = f[0]; this.denominator = f[1]; 108 /* string floating point */ 109 } else if (typeof(a) === 'string' && a.match('\.')) { 110 return new Fraction(parseFloat(a)); 111 /* whole number e.g. 'A' */ 112 } else { // just passed a whole number as a string 113 this.numerator = parseInt(a); 114 this.denominator = 1; 115 } 116 } else { 117 return undefined; // could not parse 118 } 119 } 120 } 121 this.normalize(); 122} 123 124 125Fraction.prototype.clone = function() 126{ 127 return new Fraction(this.numerator, this.denominator); 128} 129 130 131/* pretty-printer, converts fractions into whole numbers and fractions */ 132Fraction.prototype.toString = function() 133{ 134 if (this.denominator==='NaN') return 'NaN' 135 var wholepart = (this.numerator/this.denominator>0) ? 136 Math.floor(this.numerator / this.denominator) : 137 Math.ceil(this.numerator / this.denominator) 138 var numerator = this.numerator % this.denominator 139 var denominator = this.denominator; 140 var result = []; 141 if (wholepart != 0)
142 result.push(wholepart); 143 if (numerator != 0) 144 result.push(((wholepart===0) ? numerator : Math.abs(numerator)) + '/' + denominator); 145 return result.length > 0 ? result.join(' ') : 0; 146} 147 148 149/* destructively rescale the fraction by some integral factor */ 150Fraction.prototype.rescale = function(factor) 151{ 152 this.numerator *= factor; 153 this.denominator *= factor; 154 return this; 155} 156 157 158Fraction.prototype.add = function(b) 159{ 160 var a = this.clone(); 161 if (b instanceof Fraction) { 162 b = b.clone(); 163 } else { 164 b = new Fraction(b); 165 } 166 td = a.denominator; 167 a.rescale(b.denominator); 168 b.rescale(td); 169 170 a.numerator += b.numerator; 171 172 return a.normalize(); 173} 174 175 176Fraction.prototype.subtract = function(b) 177{ 178 var a = this.clone(); 179 if (b instanceof Fraction) { 180 b = b.clone(); // we scale our argument destructively, so clone 181 } else { 182 b = new Fraction(b); 183 } 184 td = a.denominator; 185 a.rescale(b.denominator); 186 b.rescale(td); 187 188 a.numerator -= b.numerator; 189 190 return a.normalize(); 191} 192 193 194Fraction.prototype.multiply = function(b) 195{ 196 var a = this.clone(); 197 if (b instanceof Fraction) 198 { 199 a.numerator *= b.numerator; 200 a.denominator *= b.denominator; 201 } else if (typeof b === 'number') { 202 a.numerator *= b; 203 } else { 204 return a.multiply(new Fraction(b)); 205 } 206 return a.normalize(); 207} 208 209Fraction.prototype.divide = function(b) 210{ 211 var a = this.clone(); 212 if (b instanceof Fraction) 213 { 214 a.numerator *= b.denominator; 215 a.denominator *= b.numerator; 216 } else if (typeof b === 'number') { 217 a.denominator *= b; 218 } else { 219 return a.divide(new Fraction(b)); 220 } 221 return a.normalize(); 222} 223 224Fraction.prototype.equals = function(b) 225{ 226 if (!(b instanceof Fraction)) { 227 b = new Fraction(b); 228 } 229 // fractions that are equal should have equal normalized forms 230 var a = this.clone().normalize(); 231 var b = b.clone().normalize(); 232 return (a.numerator === b.numerator && a.denominator === b.denominator); 233} 234 235 236/* Utility functions */ 237 238/* Destructively normalize the fraction to its smallest representation. 239 * e.g. 4/16 -> 1/4, 14/28 -> 1/2, etc. 240 * This is called after all math ops. 241 */ 242Fraction.prototype.normalize = (function() 243{ 244 245 var isFloat = function(n) 246 { 247 return (typeof(n) === 'number' && 248 ((n > 0 && n % 1 > 0 && n % 1 < 1) || 249 (n < 0 && n % -1 < 0 && n % -1 > -1)) 250 ); 251 } 252 253 var roundToPlaces = function(n, places) 254 { 255 if (!places) { 256 return Math.round(n); 257 } else { 258 var scalar = Math.pow(10, places); 259 return Math.round(n*scalar)/scalar; 260 } 261 } 262 263 return (function() { 264 265 // XXX hackish. Is there a better way to address this issue? 266 // 267 /* first check if we have decimals, and if we do eliminate them 268 * multiply by the 10 ^ number of decimal places in the number 269 * round the number to nine decimal places 270 * to avoid js floating point funnies 271 */ 272 if (isFloat(this.denominator)) { 273 var rounded = roundToPlaces(this.denominator, 9); 274 var scaleup = Math.pow(10, rounded.toString().split('.')[1].length); 275 this.denominator = Math.round(this.denominator * scaleup); // this !!! should be a whole number 276 //this.numerator *= scaleup; 277 this.numerator *= scaleup; 278 } 279 if (isFloat(this.numerator)) { 280 var rounded = roundToPlaces(this.numerator, 9); 281 var split = rounded.toString().split('.'); 282 283 var scaleup = 0; 284 if(split[1] !== undefined) { 285 scaleup = Math.pow(10, split[1].length); 286 } 287 288 this.numerator = Math.round(this.numerator * scaleup); // this !!! should be a whole number 289 //this.numerator *= scaleup; 290 this.denominator *= scaleup; 291 } 292 var gcf = Fraction.gcf(this.numerator, this.denominator); 293 this.numerator /= gcf; 294 this.denominator /= gcf; 295 if ((this.numerator < 0 && this.denominator < 0) || (this.numerator > 0 && this.denominator < 0)) { 296 this.numerator *= -1; 297 this.denominator *= -1; 298 } 299 return this; 300 }); 301 302})(); 303 304 305/* Takes two numbers and returns their greatest common factor. 306 */ 307Fraction.gcf = function(a, b) 308{ 309 310 var common_factors = []; 311 var fa = Fraction.primeFactors(a); 312 var fb = Fraction.primeFactors(b); 313 // for each factor in fa 314 // if it's also in fb 315 // put it into the common factors 316 fa.forEach(function (factor) 317 { 318 var i = fb.indexOf(factor); 319 if (i >= 0) { 320 common_factors.push(factor); 321 fb.splice(i,1); // remove from fb 322 } 323 }); 324 325 if (common_factors.length === 0) 326 return 1; 327 328 var gcf = (function() { 329 var r = common_factors[0]; 330 var i; 331 for (i=1;i<common_factors.length;i++) 332 { 333 r = r * common_factors[i]; 334 } 335 return r; 336 })(); 337 338 return gcf; 339 340}; 341 342 343// Adapted from: 344// http://www.btinternet.com/~se16/js/factor.htm 345Fraction.primeFactors = function(n) 346{ 347 348 var num = Math.abs(n); 349 var factors = []; 350 var _factor = 2; // first potential prime factor 351 352 while (_factor * _factor <= num) // should we keep looking for factors? 353 { 354 if (num % _factor === 0) // this is a factor 355 { 356 factors.push(_factor); // so keep it 357 num = num/_factor;
357 // and divide our search point by it 358 } 359 else 360 { 361 _factor++; // and increment 362 } 363 } 364 365 if (num != 1) // If there is anything left at the end... 366 { // ...this must be the last prime factor 367 factors.push(num); // so it too should be recorded 368 } 369 370 return factors; // Return the prime factors 371} 372 373 374Fraction.prototype.snap = function(max, threshold) { 375 if (!threshold) threshold = .0001; 376 if (!max) max = 100; 377 378 var negative = (this.numerator < 0); 379 var decimal = this.numerator / this.denominator; 380 var fraction = Math.abs(decimal % 1); 381 var remainder = negative ? Math.ceil(decimal) : Math.floor(decimal); 382 383 for(var denominator = 1; denominator <= max; ++denominator) { 384 for(var numerator = 0; numerator <= max; ++numerator) { 385 var approximation = Math.abs(numerator/denominator); 386 if (Math.abs(approximation - fraction) < threshold) { 387 return new Fraction(remainder * denominator + numerator * (negative ? -1 : 1), denominator); 388 } 389 } 390 } 391 392 return new Fraction(this.numerator, this.denominator); 393}; 394 395//module.exports.Fraction = Fraction
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.