UNPKG

xen-dev-utils

Version:

Utility functions used by the Scale Workshop ecosystem

1,169 lines 36.2 kB
"use strict"; // I'm rolling my own because fraction.js has trouble with TypeScript https://github.com/rawify/Fraction.js/issues/72 // -Lumi Object.defineProperty(exports, "__esModule", { value: true }); exports.Fraction = exports.modc = exports.mmod = exports.lcm = exports.gcd = void 0; const conversion_1 = require("./conversion"); const primes_1 = require("./primes"); const MAX_CONTINUED_LENGTH = 1000; const MAX_CYCLE_LENGTH = 128; function gcd(a, b) { if (!a) return b; if (!b) return a; while (true) { // XXX: TypeScript trips up here for no reason. // @ts-ignore a %= b; if (!a) return b; // @ts-ignore b %= a; if (!b) return a; } } exports.gcd = gcd; function lcm(a, b) { // @ts-ignore return a ? (a / gcd(a, b)) * b : a; } exports.lcm = lcm; function mmod(a, b) { // @ts-ignore return ((a % b) + b) % b; } exports.mmod = mmod; function modc(a, b) { if (!b) { return b; } // @ts-ignore return ((a % b) + b) % b || b; } exports.modc = modc; /** * * This class offers the possibility to calculate fractions. * You can pass a fraction in different formats: either as two integers, an integer, a floating point number or a string. * * Numerator, denominator form * ```ts * new Fraction(numerator, denominator); * ``` * * Integer form * ```ts * new Fraction(numerator); * ``` * * Floating point form * ```ts * new Fraction(value); * ``` * * String form * ```ts * new Fraction("123.456"); // a simple decimal * new Fraction("123/456"); // a string fraction * new Fraction("13e-3"); // scientific notation * ``` */ class Fraction { constructor(numerator, denominator) { if (denominator !== undefined) { if (typeof numerator !== 'number') { throw new Error('Numerator must be a number when denominator is given'); } if (isNaN(numerator) || isNaN(denominator)) { throw new Error('Cannot represent NaN as a fraction'); } this.s = Math.sign(numerator * denominator); this.n = Math.abs(numerator); this.d = Math.abs(denominator); this.screenInfinity(); if (this.d > Number.MAX_SAFE_INTEGER) { throw new Error('Denominator above safe limit'); } if (this.n > Number.MAX_SAFE_INTEGER) { if (!isFinite(this.n)) { throw new Error('Cannot represent Infinity as a fraction'); } throw new Error('Numerator above safe limit'); } if (this.d === 0) { throw new Error('Division by Zero'); } this.defloat(); } else if (typeof numerator === 'number') { if (isNaN(numerator)) { throw new Error('Cannot represent NaN as a fraction'); } this.s = Math.sign(numerator); this.n = Math.abs(numerator); this.d = 1; if (!isFinite(this.n)) { throw new Error('Cannot represent Infinity as a fraction'); } this.defloat(); } else if (typeof numerator === 'string') { numerator = numerator.toLowerCase(); this.d = 1; let exponent; if (numerator.includes('e')) { [numerator, exponent] = numerator.split('e', 2); } if (numerator.includes('/')) { this.n = 1; if (numerator.includes('.')) { throw new Error('Parameters must be integer'); } const [n, d] = numerator.split('/', 2); if (n === '-') { this.n *= -1; } else { this.n *= n ? parseInt(n, 10) : 1; } this.d *= d ? parseInt(d, 10) : 1; this.s = Math.sign(this.n * this.d); this.n = Math.abs(this.n); this.d = Math.abs(this.d); } else if (numerator.includes('.')) { let [n, f] = numerator.split('.', 2); let r; [f, r] = f.split("'", 2); if (n.startsWith('-')) { this.s = -1; n = n.slice(1); } else { this.s = 1; } if (n.startsWith('-')) { throw new Error('Double sign'); } this.n = 0; for (const c of f.split('').reverse()) { this.n += this.d * parseInt(c, 10); this.d *= 10; if (this.d > Number.MAX_SAFE_INTEGER) { throw new Error('Decimal string too complex'); } const factor = gcd(this.n, this.d); this.n /= factor; this.d /= factor; } if (n) { this.n += parseInt(n, 10) * this.d; } if (r) { r = r.replace(/'/g, ''); if (r.length) { const cycleN = parseInt(r, 10); if (cycleN > Number.MAX_SAFE_INTEGER) { throw new Error('Cycle too long'); } const cycleD = (10 ** r.length - 1) * 10 ** f.length; const factor = gcd(cycleD, this.d); this.d /= factor; this.n = this.n * (cycleD / factor) + this.d * cycleN; this.d *= cycleD; } } if (!this.n) { this.s = 0; } } else { this.n = parseInt(numerator, 10); this.s = Math.sign(this.n); this.n = Math.abs(this.n); } if (this.d === 0) { throw new Error('Division by Zero'); } if (exponent) { const e = parseInt(exponent, 10); if (e > 0) { this.n *= 10 ** e; } else if (e < 0) { this.d *= 10 ** -e; } } this.validate(); this.reduce(); } else { if (numerator.d === 0) { throw new Error('Division by Zero'); } if ('s' in numerator) { this.s = Math.sign(numerator.s); } else { this.s = 1; } if (numerator.d < 0) { this.s = -this.s; } if (numerator.n < 0) { this.s = -this.s; } else if (numerator.n === 0) { this.s = 0; } this.n = Math.abs(numerator.n); this.d = Math.abs(numerator.d); this.screenInfinity(); this.validate(); this.reduce(); } } /** * Validate that this fraction represents the ratio of two integers. */ validate() { if (isNaN(this.s) || isNaN(this.n) || isNaN(this.d)) { throw new Error('Cannot represent NaN as a fraction'); } if (this.n > Number.MAX_SAFE_INTEGER) { if (!isFinite(this.n)) { throw new Error('Cannot represent Infinity as a fraction'); } throw new Error('Numerator above safe limit'); } if (this.d > Number.MAX_SAFE_INTEGER) { throw new Error('Denominator above safe limit'); } } /** * IEEE floats always have a denominator of a power of two. Reduce it out. * If the process would produce integers too large for the Number type an approximation is used. */ defloat() { while (this.n !== Math.floor(this.n) || this.d !== Math.floor(this.d)) { this.n *= 2; this.d *= 2; } // Cut back if defloating produces something not representable additively. if (this.n > Number.MAX_SAFE_INTEGER || this.d > Number.MAX_SAFE_INTEGER) { let x = this.n / this.d; const coefs = []; for (let i = 0; i < 20; ++i) { const coef = Math.floor(x); if (coef > 1e12) { break; } coefs.push(coef); if (x === coef) { break; } x = 1 / (x - coef); } if (!coefs.length) { throw new Error('Numerator above safe limit'); } let j = 1; while (j <= coefs.length) { let n = coefs[coefs.length - j]; let d = 1; for (let i = coefs.length - j - 1; i >= 0; --i) { [n, d] = [d + n * coefs[i], n]; } this.n = n; this.d = d; if (n <= Number.MAX_SAFE_INTEGER && d <= Number.MAX_SAFE_INTEGER) { break; } j++; } } this.reduce(); } /** * Reduce out the common factor between the numerator and denominator. */ reduce() { const commonFactor = gcd(this.n, this.d); this.n /= commonFactor; this.d /= commonFactor; } /** * Normalize infinite denominator into 0/1. */ screenInfinity() { if (!isFinite(this.d)) { if (!isFinite(this.n)) { throw new Error('Cannot represent NaN as a fraction'); } this.s = 0; this.n = 0; this.d = 1; } } /** * Creates a string representation of a fraction with all digits. * * Example: * ```ts * new Fraction("100.'91823'").toString() // "100.'91823'" * ``` **/ toString() { let result = this.s < 0 ? '-' : ''; const whole = Math.floor(this.n / this.d); result += whole.toString(); let fractional = this.abs().sub(whole); if (fractional.n === 0) { return result; } result += '.'; let decimals = ''; const history = [fractional]; for (let i = 0; i < MAX_CYCLE_LENGTH; ++i) { fractional = fractional.mul(10); const digit = Math.floor(fractional.n / fractional.d); decimals += digit.toString(); fractional = fractional.sub(digit); if (fractional.n === 0) { return result + decimals; } for (let j = 0; j < history.length; ++j) { if (fractional.equals(history[j])) { return result + decimals.slice(0, j) + "'" + decimals.slice(j) + "'"; } } history.push(fractional); } return result + decimals + '...'; } /** * Serialize the fraction to a JSON compatible object. * @returns An object with properties 'n', and 'd' corresponding to a signed numerator and an unsigned denominator respectively. */ toJSON() { return { n: this.n * this.s, d: this.d, }; } /** * Revive a {@link Fraction} instance produced by Fraction.toJSON(). Return everything else as is. * * Intended usage: * ```ts * const data = JSON.parse(serializedData, Fraction.reviver); * ``` * * @param key Property name. * @param value Property value. * @returns Deserialized {@link Fraction} instance or other data without modifications. * @throws An error if the numerator or denominator exceeds `Number.MAX_SAFE_INTEGER`. */ static reviver(key, value) { if (typeof value === 'object' && value !== null && 'n' in value && Number.isInteger(value.n) && 'd' in value && Number.isInteger(value.d) && Object.keys(value).length === 2) { return new Fraction(value); } return value; } /** * Returns an array of continued fraction elements. * * Example: * ```ts * new Fraction("7/8").toContinued() // [0, 1, 7] * ``` */ toContinued() { const result = []; let a = this.n; let b = this.d; for (let i = 0; i < MAX_CONTINUED_LENGTH; ++i) { const coef = Math.floor(a / b); result.push(coef); [a, b] = [b, a - coef * b]; if (a === 1) { break; } } return result; } /** * Calculates the absolute value. * * Example: * ```ts * new Fraction(-4).abs() // 4 * ``` **/ abs() { return new Fraction({ s: Math.abs(this.s), n: this.n, d: this.d, }); } /** * Returns a decimal representation of the fraction. * * Example: * ```ts * new Fraction("100.'91823'").valueOf() // 100.91823918239183 * ``` **/ valueOf() { return (this.s * this.n) / this.d; } /** * Returns the inverse of the fraction, numerator and denominator are exchanged. * * Example: * ```ts * new Fraction(-3, 4).inverse() // -4/3 * ``` **/ inverse() { if (this.n === 0) { throw new Error('Division by Zero'); } return new Fraction({ s: this.s, n: this.d, d: this.n }); } /** * Returns the additive inverse of the fraction. * * Example: * ```ts * new Fraction(-4).neg() // 4 * ``` **/ neg() { return new Fraction({ s: -this.s, n: this.n, d: this.d }); } /** * Returns a string-fraction representation of a Fraction object. * * Example: * ```ts * new Fraction("1.'3'").toFraction() // "4/3" * ``` **/ toFraction() { const n = this.s * this.n; if (this.d === 1) { return n.toString(); } return `${n}/${this.d}`; } /** * Clones the actual object. * * Example: * ```ts * new Fraction("-17.'345'").clone() // new Fraction("-17.'345'") * ``` **/ clone() { return new Fraction(this); } /** * Return a convergent of this fraction that is within the given absolute tolerance. * @param epsilon Absolute tolerance for error. */ simplify(epsilon = 0.001) { const abs = this.abs(); const cont = abs.toContinued(); const absValue = abs.valueOf(); for (let i = 1; i < cont.length; i++) { let s = new Fraction({ s: 1, n: cont[i - 1], d: 1 }); for (let k = i - 2; k >= 0; k--) { s = s.inverse().add(cont[k]); } if (Math.abs(s.valueOf() - absValue) <= epsilon) { return new Fraction({ s: this.s, n: s.n, d: s.d }); } } return this.clone(); } /** * Return a convergent of this fraction that is within the given relative tolerance measured in cents. * @param tolerance Relative tolerance measured in cents. */ simplifyRelative(tolerance = 3.5) { const abs = this.abs(); const cont = abs.toContinued(); const absCents = (0, conversion_1.valueToCents)(abs.valueOf()); for (let i = 1; i < cont.length; i++) { let s = new Fraction({ s: 1, n: cont[i - 1], d: 1 }); for (let k = i - 2; k >= 0; k--) { s = s.inverse().add(cont[k]); } if (Math.abs((0, conversion_1.valueToCents)(s.valueOf()) - absCents) <= tolerance) { return new Fraction({ s: this.s, n: s.n, d: s.d }); } } return this.clone(); } /** * Calculates the floor of a rational number. * * Example: * ```ts * new Fraction("4.'3'").floor() // 4/1 * ``` **/ floor() { if (this.d > Number.MAX_SAFE_INTEGER) { return new Fraction(Math.floor(this.valueOf())); } const n = this.s * this.n; const m = mmod(n, this.d); return new Fraction((n - m) / this.d); } /** * Calculates the ceil of a rational number. * * Example: * ```ts * new Fraction("4.'3'").ceil() // 5/1 * ``` **/ ceil() { if (this.d > Number.MAX_SAFE_INTEGER) { return new Fraction(Math.ceil(this.valueOf())); } const n = this.s * this.n; const m = mmod(n, this.d); if (m) { return new Fraction(1 + (n - m) / this.d); } return this; } /** * Rounds a rational number. * * Examples: * ```ts * new Fraction("4.'3'").floor() // 4/1 * new Fraction("4.5").floor() // 5/1 * new Fraction("4.'6'").floor() // 5/1 * ``` **/ round() { try { return this.add(new Fraction({ s: 1, n: 1, d: 2 })).floor(); } catch { return new Fraction(Math.round(this.valueOf())); } } /** * Rounds a rational number to a multiple of another rational number. * * Examples: * ```ts * new Fraction("0.'7'").roundTo("1/9") // 7/9 * new Fraction("0.78").roundTo("1/9") // 7/9 * new Fraction("0.8'3'").roundTo("1/9") // 8/9 * new Fraction("0.85").roundTo("1/9") // 8/9 * ``` **/ roundTo(other) { const { n, d } = new Fraction(other); return new Fraction(this.s * Math.round((this.n * d) / (this.d * n)) * n, d); } /** * Adds two rational numbers. * * Example: * ```ts * new Fraction({n: 2, d: 3}).add("14.9") // 467/30 * ``` **/ add(other) { const { s, n, d } = new Fraction(other); // Must pre-reduce to avoid blowing the limits const factor = gcd(this.d, d); const df = d / factor; return new Fraction(this.s * this.n * df + s * n * (this.d / factor), df * this.d); } /** * Subtracts two rational numbers. * * Example: * ```ts * new Fraction({n: 2, d: 3}).sub("14.9") // -427/30 * ``` **/ sub(other) { const { s, n, d } = new Fraction(other); // Must pre-reduce to avoid blowing the limits const factor = gcd(this.d, d); const df = d / factor; return new Fraction(this.s * this.n * df - s * n * (this.d / factor), df * this.d); } /** * Perform harmonic addition of two rational numbers according to the thin lens equation f⁻¹ = u⁻¹ + v⁻¹. * * Example: * ```ts * new Fraction('5/3').lensAdd('3/2') // 15/19 * ``` */ lensAdd(other) { const { s, n, d } = new Fraction(other); if (!n || !this.n) { // Based on behavior in the limit where both terms become zero. return new Fraction({ s: 0, n: 0, d: 1 }); } // Must pre-reduce to avoid blowing the limits const numerator = lcm(this.n, n); return new Fraction(this.s * s * numerator, (numerator / n) * d + (numerator / this.n) * this.d); } /** * Perform harmonic subtraction of two rational numbers u⁻¹ = f⁻¹ - v⁻¹ (rearranged thin lens equation). * * Example: * ```ts * new Fraction('15/19').lensSub('3/2') // 5/3 * ``` */ lensSub(other) { const { s, n, d } = new Fraction(other); if (!n || !this.n) { // Based on behavior in the limit where both terms become zero. return new Fraction({ s: 0, n: 0, d: 1 }); } // Must pre-reduce to avoid blowing the limits const numerator = lcm(this.n, n); return new Fraction(this.s * s * numerator, (numerator / this.n) * this.d - (numerator / n) * d); } /** * Multiplies two rational numbers. * * Example: * ```ts * new Fraction("-17.'345'").mul(3) // 5776/111 * ``` **/ mul(other) { const { s, n, d } = new Fraction(other); // Must pre-reduce to avoid blowing the limits const ndFactor = gcd(this.n, d); const dnFactor = gcd(this.d, n); return new Fraction(this.s * (this.n / ndFactor) * s * (n / dnFactor), (this.d / dnFactor) * (d / ndFactor)); } /** * Divides two rational numbers * * Example: * ```ts * new Fraction("-17.'345'").div(3) // 5776/999 * ``` **/ div(other) { const { s, n, d } = new Fraction(other); if (n === 0) { throw new Error('Division by Zero'); } // Must pre-reduce to avoid blowing the limits const nFactor = gcd(this.n, n); const dFactor = gcd(this.d, d); return new Fraction(this.s * (this.n / nFactor) * s * (d / dFactor), (this.d / dFactor) * (n / nFactor)); } /** * Calculates the computational modulo of two rational numbers - a more precise fmod. Incorrectly processes signs. * * Examples: * ```ts * new Fraction("5/1").mod("3/1") // (5/1) % (3/1) = 2/1 * new Fraction("-5/1").mod("3/1") // -((5/1) % (3/1)) = -2/1 * ``` **/ mod(other) { const { n, d } = new Fraction(other); // Must pre-reduce to avoid blowing the limits const denominator = lcm(this.d, d); return new Fraction((this.s * ((denominator / this.d) * this.n)) % (n * (denominator / d)), denominator); } /** * Calculates the mathematical modulo of two rational numbers. Correctly processes signs. * * Examples: * ```ts * new Fraction("5/1").mod("3/1") // (5/1) % (3/1) = 2/1 * new Fraction("-5/1").mod("3/1") // (-5/1) % (3/1) = (1/1) % (3/1) = 1/1 * ``` **/ mmod(other) { const { n, d } = new Fraction(other); // Must pre-reduce to avoid blowing the limits const denominator = lcm(this.d, d); return new Fraction(mmod(this.s * ((denominator / this.d) * this.n), n * (denominator / d)), denominator); } /** * Calculates the square root of the rational number. * * Examples: * ```ts * new Fraction("9/4").sqrt() // 3/2 * new Fraction(-1).sqrt() // null * ``` * @returns The positive square root if it exists as a rational number. */ sqrt() { if (this.s < 0) { return null; } const n = Math.round(Math.sqrt(this.n)); if (n * n !== this.n) { return null; } const d = Math.round(Math.sqrt(this.d)); if (d * d !== this.d) { return null; } return new Fraction(n, d); } /** * Calculates the fraction to some rational exponent, if possible. * * Examples: * ```ts * new Fraction("1/2").pow(2) // 1/4 * new Fraction("-1/2").pow(-3) // -8 * new Fraction("9/4").pow("3/2") // 27/8 * new Fraction("2/1").pow("1/2") // null * ``` */ pow(other) { const { s, n, d } = new Fraction(other); if (s === 0) { return new Fraction(1); } if (d === 1) { if (s < 0) { return new Fraction((this.s * this.d) ** n, this.n ** n); } else { return new Fraction((this.s * this.n) ** n, this.d ** n); } } if (this.s === 0) { return new Fraction(0); } if (this.s < 0 && d % 2 === 0) { return null; } if (this.n === 1) { if (this.s > 0) { return new Fraction(1); } return new Fraction(-1); } if (n === 1 && d === 2) { const sqrt = this.sqrt(); if (sqrt) { return s > 0 ? sqrt : sqrt.inverse(); } return null; } let nProbe = 1; let dProbe = 1; let limitIndex = 0; let numerator = n % 2 ? this.s : 1; let denominator = 1; do { if (limitIndex >= primes_1.PRIMES.length) { return null; } let rootExponent = -1; const prime = primes_1.PRIMES[limitIndex]; const primePower = prime ** d; let lastProbe; do { lastProbe = nProbe; nProbe *= primePower; rootExponent++; } while (this.n % nProbe === 0); nProbe = lastProbe; for (let i = 1; i < d; ++i) { lastProbe *= prime; if (this.n % lastProbe === 0) { return null; } } // The fraction is in lowest terms so we can skip the denominator if (rootExponent) { numerator *= prime ** (n * rootExponent); limitIndex++; continue; } rootExponent = -1; do { lastProbe = dProbe; dProbe *= primePower; rootExponent++; } while (this.d % dProbe === 0); dProbe = lastProbe; for (let i = 1; i < d; ++i) { lastProbe *= prime; if (this.d % lastProbe === 0) { return null; } } denominator *= prime ** (n * rootExponent); limitIndex++; } while (nProbe !== this.n || dProbe !== this.d); if (s > 0) { return new Fraction(numerator, denominator); } return new Fraction(denominator, numerator); } /** * Compare two rational numbers, returns (this - other) which has the correct sign for `Array.sort()`. * * Examples: * ```ts * new Fraction("19.7").compare("98/5") // 0.1 * new Fraction("19.6").compare("98/5") // 0 * new Fraction("19.5").compare("98/5") // -0.1 * ``` **/ compare(other) { try { const { s, n, d } = new Fraction(other); return this.s * this.n * d - s * n * this.d; } catch { return NaN; } } /** * Check if two rational numbers are the same * * Examples: * ```ts * new Fraction("19.7").equals("98/5") // false * new Fraction("19.6").equals("98/5") // true * new Fraction("19.5").equals("98/5") // false * ``` **/ equals(other) { try { const { s, n, d } = new Fraction(other); return this.s === s && this.n === n && this.d === d; } catch { return false; } } /** * Check if two rational numbers are divisible * (i.e. this is an integer multiple of other) * * Examples: * ```ts * new Fraction("7.6").divisible("5/2") // false * new Fraction("7.5").divisible("5/2") // true * new Fraction("7/4").divisible("5/2") // false * ``` */ divisible(other) { try { const { n, d } = new Fraction(other); const nFactor = gcd(this.n, n); const dFactor = gcd(this.d, d); return !(!n || ((this.n / nFactor) * (d / dFactor)) % ((n / nFactor) * (this.d / dFactor))); } catch { return false; } } /** * Calculates the fractional gcd of two rational numbers. (i.e. both this and other is divisible by the result) * * Always returns a non-negative result. * * Example: * ```ts * new Fraction(5,8).gcd("3/7") // 1/56 * ``` */ gcd(other) { const { n, d } = new Fraction(other); return new Fraction(gcd(n, this.n), lcm(this.d, d)); } /** * Calculates the fractional lcm of two rational numbers. (i.e. the result is divisible by both this and other) * * Has the same sign as the product of the rational numbers. * * Example: * ```ts * new Fraction(5,8).gcd("3/7") // 15 * ``` */ lcm(other) { const { s, n, d } = new Fraction(other); const result = new Fraction(lcm(n, this.n), gcd(d, this.d)); result.s = this.s * s; return result; } /** * Geometrically reduce a rational number until its absolute value is between 1 and the absolute value of other, i.e. geometric modulo. * Note: Returns a positive result for a negative modulo if the required number of divisions is even. * * Examples: * ```ts * new Fraction(5, 1).geoMod(2) // 5/4 * new Fraction(1, 11).geoMod(3) // 27/11 * new Fraction(1, 11).geoMod("-1/3") // 9/11 * ``` */ geoMod(other) { let { s, n, d } = this; const { s: os, n: on, d: od } = new Fraction(other); if (on === od) { throw new Error('Geometric modulo by 1'); } let octaves = Math.floor(Math.log(n / d) / Math.log(on / od)); if (isNaN(octaves) || !isFinite(octaves)) { throw new Error('Unable to calculate geometric modulo.'); } if (octaves > 0) { n *= od ** octaves; d *= on ** octaves; } else if (octaves < 0) { n *= on ** -octaves; d *= od ** -octaves; } // Fine-tune to fix floating point issues. if (on > od) { if (n * od >= d * on) { octaves++; n *= od; d *= on; } if (n < d) { octaves--; n *= on; d *= od; } } else { if (n * od <= d * on) { octaves++; n *= od; d *= on; } if (n > d) { octaves--; n *= on; d *= od; } } s *= os ** octaves; return new Fraction({ s, n, d }); } /** * Check if the rational number is 1. * * Examples: * ```ts * new Fraction(9, 9).isUnity() // true * new Fraction("0.01e2").isUnity() // true * new Fraction(7, 6).isUnity() // false * ``` */ isUnity() { return this.s === 1 && this.n === 1 && this.d === 1; } /** * Calculates the geometric absolute value. Discards sign. * * Examples: * ```ts * new Fraction(3, 2).gabs() // 3/2 * new Fraction(2, 3).gabs() // 2/3 * new Fraction(-1, 2).gabs() // 2/1 * ``` **/ gabs() { if (this.n < this.d) { return new Fraction({ n: this.d, d: this.n }); } return this.abs(); } /** * Calculate the greatest common radical between two rational numbers if it exists. * * Never returns a subunitary result. * * Treats unity as the identity element: gcr(1, x) = gcr(x, 1) = x * * Examples: * ```ts * new Fraction(8).gcr(4) // 2 * new Fraction(81).gcr(6561) // 9 * new Fraction("1/2").gcr("1/3") // null * ``` */ gcr(other, maxIter = 100) { let a = this.gabs(); let b = new Fraction(other).gabs(); if (a.isUnity()) return b; if (b.isUnity()) return a; for (let i = 0; i < maxIter; ++i) { try { a = a.geoMod(b); if (a.isUnity()) return b; b = b.geoMod(a); if (b.isUnity()) return a; } catch { return null; } } return null; } /** * Calculate the logarithm of a rational number in the base of another, i.e. logdivision if the result exists as a rational number. * * Examples: * ```ts * new Fraction(4).log(2) // 2 * new Fraction(64,27).log("16/9") // 3/2 * new Fraction(64,27).log("1/1") // null * new Fraction(64,27).log(7) // null * * new Fraction(64,27).log("-16/9") // null * new Fraction(-64,27).log("16/9") // null * new Fraction(-64,27).log("-16/9") // null * ``` */ log(other, maxIter = 100) { const other_ = new Fraction(other); if (other_.isUnity()) { if (this.isUnity()) { // This convention follows from an identity between gcr and lcr // Not entirely well-founded, but not entirely wrong either. return new Fraction(1); } return null; } const radical = this.gcr(other_, maxIter); if (radical === null) { return null; } const base = 1 / Math.log(radical.n / radical.d); const n = Math.round(Math.log(this.n / this.d) * base); const d = Math.round(Math.log(other_.n / other_.d) * base); if (other_.s < 0) { if (d % 2 === 0) { return null; } if (n % 2) { if (this.s > 0) { return null; } } else { if (this.s < 0) { return null; } } } else if (this.s < 0) { return null; } return new Fraction({ n, d }); } /** * Calculate the least common radicand between two rational numbers if it exists. * * If either of the inputs is unitary returns unity (1). * * Returns a subunitary result if only one of the inputs is subunitary, superunitary otherwise. * * Examples: * ```ts * new Fraction(8).lcr(4) // 64 * new Fraction("1/2").lcr("1/3") // null * ``` */ lcr(other, maxIter = 100) { const other_ = new Fraction(other); const radical = this.gcr(other, maxIter); if (radical === null) { return null; } if (radical.isUnity()) { return new Fraction(1); } const base = 1 / Math.log(radical.n / radical.d); const n = Math.round(Math.log(this.n / this.d) * base); const d = Math.round(Math.log(other_.n / other_.d) * base); return radical.pow(n * d); } /** * Rounds a rational number to a power of another rational number. * * Examples: * ```ts * new Fraction('5/4').geoRoundTo("9/8") // 81/64 * new Fraction('5/4').geoRoundTo("1/1") // 1/1 * * // handling negative values * * new Fraction('5/4').geoRoundTo("-9/8") // 81/64 * * new Fraction('10/7').geoRoundTo("9/8") // 729/512 * new Fraction('10/7').geoRoundTo("-9/8") // 6561/4096 * * new Fraction('-5/4').geoRoundTo("9/8") // null * * new Fraction('-5/4').geoRoundTo("-9/8") // -9/8 * ``` */ geoRoundTo(other) { const other_ = new Fraction(other); let exponent = Math.log(this.n / this.d) / Math.log(other_.n / other_.d); if (this.s === 0) { return this.clone(); } if (this.s < 0) { if (other_.s > 0) { return null; } exponent = Math.round((exponent + 1) * 0.5) * 2 - 1; } else if (other_.s < 0) { exponent = Math.round(exponent * 0.5) * 2; } else { exponent = Math.round(exponent); } return other_.pow(exponent); } } exports.Fraction = Fraction; //# sourceMappingURL=fraction.js.map