diff --git a/.gitignore b/.gitignore index 9daa824..8d67a86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store node_modules +dist diff --git a/dist/fraction.js b/dist/fraction.js deleted file mode 100644 index 816d5db..0000000 --- a/dist/fraction.js +++ /dev/null @@ -1,1045 +0,0 @@ -'use strict'; - -/** - * - * This class offers the possibility to calculate fractions. - * You can pass a fraction in different formats. Either as array, as double, as string or as an integer. - * - * Array/Object form - * [ 0 => , 1 => ] - * { n => , d => } - * - * Integer form - * - Single integer value as BigInt or Number - * - * Double form - * - Single double value as Number - * - * String form - * 123.456 - a simple double - * 123/456 - a string fraction - * 123.'456' - a double with repeating decimal places - * 123.(456) - synonym - * 123.45'6' - a double with repeating last place - * 123.45(6) - synonym - * - * Example: - * let f = new Fraction("9.4'31'"); - * f.mul([-4, 3]).div(4.9); - * - */ - -// Set Identity function to downgrade BigInt to Number if needed -if (typeof BigInt === 'undefined') BigInt = function (n) { if (isNaN(n)) throw new Error(""); return n; }; - -const C_ZERO = BigInt(0); -const C_ONE = BigInt(1); -const C_TWO = BigInt(2); -const C_THREE = BigInt(3); -const C_FIVE = BigInt(5); -const C_TEN = BigInt(10); -const MAX_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); - -// Maximum search depth for cyclic rational numbers. 2000 should be more than enough. -// Example: 1/7 = 0.(142857) has 6 repeating decimal places. -// If MAX_CYCLE_LEN gets reduced, long cycles will not be detected and toString() only gets the first 10 digits -const MAX_CYCLE_LEN = 2000; - -// Parsed data to avoid calling "new" all the time -const P = { - "s": C_ONE, - "n": C_ZERO, - "d": C_ONE -}; - -function assign(n, s) { - - try { - n = BigInt(n); - } catch (e) { - throw InvalidParameter(); - } - return n * s; -} - -function ifloor(x) { - return typeof x === 'bigint' ? x : Math.floor(x); -} - -// Creates a new Fraction internally without the need of the bulky constructor -function newFraction(n, d) { - - if (d === C_ZERO) { - throw DivisionByZero(); - } - - const f = Object.create(Fraction.prototype); - f["s"] = n < C_ZERO ? -C_ONE : C_ONE; - - n = n < C_ZERO ? -n : n; - - const a = gcd(n, d); - - f["n"] = n / a; - f["d"] = d / a; - return f; -} - -const FACTORSTEPS = [C_TWO * C_TWO, C_TWO, C_TWO * C_TWO, C_TWO, C_TWO * C_TWO, C_TWO * C_THREE, C_TWO, C_TWO * C_THREE]; // repeats -function factorize(n) { - - const factors = Object.create(null); - if (n <= C_ONE) { - factors[n] = C_ONE; - return factors; - } - - const add = (p) => { factors[p] = (factors[p] || C_ZERO) + C_ONE; }; - - while (n % C_TWO === C_ZERO) { add(C_TWO); n /= C_TWO; } - while (n % C_THREE === C_ZERO) { add(C_THREE); n /= C_THREE; } - while (n % C_FIVE === C_ZERO) { add(C_FIVE); n /= C_FIVE; } - - // 30-wheel trial division: test only residues coprime to 2*3*5 - // Residue step pattern after 5: 7,11,13,17,19,23,29,31, ... - for (let si = 0, p = C_TWO + C_FIVE; p * p <= n;) { - while (n % p === C_ZERO) { add(p); n /= p; } - p += FACTORSTEPS[si]; - si = (si + 1) & 7; // fast modulo 8 - } - if (n > C_ONE) add(n); - return factors; -} - -const parse = function (p1, p2) { - - let n = C_ZERO, d = C_ONE, s = C_ONE; - - if (p1 === undefined || p1 === null) { // No argument - /* void */ - } else if (p2 !== undefined) { // Two arguments - - if (typeof p1 === "bigint") { - n = p1; - } else if (isNaN(p1)) { - throw InvalidParameter(); - } else if (p1 % 1 !== 0) { - throw NonIntegerParameter(); - } else { - n = BigInt(p1); - } - - if (typeof p2 === "bigint") { - d = p2; - } else if (isNaN(p2)) { - throw InvalidParameter(); - } else if (p2 % 1 !== 0) { - throw NonIntegerParameter(); - } else { - d = BigInt(p2); - } - - s = n * d; - - } else if (typeof p1 === "object") { - if ("d" in p1 && "n" in p1) { - n = BigInt(p1["n"]); - d = BigInt(p1["d"]); - if ("s" in p1) - n *= BigInt(p1["s"]); - } else if (0 in p1) { - n = BigInt(p1[0]); - if (1 in p1) - d = BigInt(p1[1]); - } else if (typeof p1 === "bigint") { - n = p1; - } else { - throw InvalidParameter(); - } - s = n * d; - } else if (typeof p1 === "number") { - - if (isNaN(p1)) { - throw InvalidParameter(); - } - - if (p1 < 0) { - s = -C_ONE; - p1 = -p1; - } - - if (p1 % 1 === 0) { - n = BigInt(p1); - } else { - - let z = 1; - - let A = 0, B = 1; - let C = 1, D = 1; - - let N = 10000000; - - if (p1 >= 1) { - z = 10 ** Math.floor(1 + Math.log10(p1)); - p1 /= z; - } - - // Using Farey Sequences - - while (B <= N && D <= N) { - let M = (A + C) / (B + D); - - if (p1 === M) { - if (B + D <= N) { - n = A + C; - d = B + D; - } else if (D > B) { - n = C; - d = D; - } else { - n = A; - d = B; - } - break; - - } else { - - if (p1 > M) { - A += C; - B += D; - } else { - C += A; - D += B; - } - - if (B > N) { - n = C; - d = D; - } else { - n = A; - d = B; - } - } - } - n = BigInt(n) * BigInt(z); - d = BigInt(d); - } - - } else if (typeof p1 === "string") { - - let ndx = 0; - - let v = C_ZERO, w = C_ZERO, x = C_ZERO, y = C_ONE, z = C_ONE; - - let match = p1.replace(/_/g, '').match(/\d+|./g); - - if (match === null) - throw InvalidParameter(); - - if (match[ndx] === '-') {// Check for minus sign at the beginning - s = -C_ONE; - ndx++; - } else if (match[ndx] === '+') {// Check for plus sign at the beginning - ndx++; - } - - if (match.length === ndx + 1) { // Check if it's just a simple number "1234" - w = assign(match[ndx++], s); - } else if (match[ndx + 1] === '.' || match[ndx] === '.') { // Check if it's a decimal number - - if (match[ndx] !== '.') { // Handle 0.5 and .5 - v = assign(match[ndx++], s); - } - ndx++; - - // Check for decimal places - if (ndx + 1 === match.length || match[ndx + 1] === '(' && match[ndx + 3] === ')' || match[ndx + 1] === "'" && match[ndx + 3] === "'") { - w = assign(match[ndx], s); - y = C_TEN ** BigInt(match[ndx].length); - ndx++; - } - - // Check for repeating places - if (match[ndx] === '(' && match[ndx + 2] === ')' || match[ndx] === "'" && match[ndx + 2] === "'") { - x = assign(match[ndx + 1], s); - z = C_TEN ** BigInt(match[ndx + 1].length) - C_ONE; - ndx += 3; - } - - } else if (match[ndx + 1] === '/' || match[ndx + 1] === ':') { // Check for a simple fraction "123/456" or "123:456" - w = assign(match[ndx], s); - y = assign(match[ndx + 2], C_ONE); - ndx += 3; - } else if (match[ndx + 3] === '/' && match[ndx + 1] === ' ') { // Check for a complex fraction "123 1/2" - v = assign(match[ndx], s); - w = assign(match[ndx + 2], s); - y = assign(match[ndx + 4], C_ONE); - ndx += 5; - } - - if (match.length <= ndx) { // Check for more tokens on the stack - d = y * z; - s = /* void */ - n = x + d * v + z * w; - } else { - throw InvalidParameter(); - } - - } else if (typeof p1 === "bigint") { - n = p1; - s = p1; - d = C_ONE; - } else { - throw InvalidParameter(); - } - - if (d === C_ZERO) { - throw DivisionByZero(); - } - - P["s"] = s < C_ZERO ? -C_ONE : C_ONE; - P["n"] = n < C_ZERO ? -n : n; - P["d"] = d < C_ZERO ? -d : d; -}; - -function modpow(b, e, m) { - - let r = C_ONE; - for (; e > C_ZERO; b = (b * b) % m, e >>= C_ONE) { - - if (e & C_ONE) { - r = (r * b) % m; - } - } - return r; -} - -function cycleLen(n, d) { - - for (; d % C_TWO === C_ZERO; - d /= C_TWO) { - } - - for (; d % C_FIVE === C_ZERO; - d /= C_FIVE) { - } - - if (d === C_ONE) // Catch non-cyclic numbers - return C_ZERO; - - // If we would like to compute really large numbers quicker, we could make use of Fermat's little theorem: - // 10^(d-1) % d == 1 - // However, we don't need such large numbers and MAX_CYCLE_LEN should be the capstone, - // as we want to translate the numbers to strings. - - let rem = C_TEN % d; - let t = 1; - - for (; rem !== C_ONE; t++) { - rem = rem * C_TEN % d; - - if (t > MAX_CYCLE_LEN) - return C_ZERO; // Returning 0 here means that we don't print it as a cyclic number. It's likely that the answer is `d-1` - } - return BigInt(t); -} - -function cycleStart(n, d, len) { - - let rem1 = C_ONE; - let rem2 = modpow(C_TEN, len, d); - - for (let t = 0; t < 300; t++) { // s < ~log10(Number.MAX_VALUE) - // Solve 10^s == 10^(s+t) (mod d) - - if (rem1 === rem2) - return BigInt(t); - - rem1 = rem1 * C_TEN % d; - rem2 = rem2 * C_TEN % d; - } - return 0; -} - -function gcd(a, b) { - - if (!a) - return b; - if (!b) - return a; - - while (1) { - a %= b; - if (!a) - return b; - b %= a; - if (!b) - return a; - } -} - -/** - * Module constructor - * - * @constructor - * @param {number|Fraction=} a - * @param {number=} b - */ -function Fraction(a, b) { - - parse(a, b); - - if (this instanceof Fraction) { - a = gcd(P["d"], P["n"]); // Abuse a - this["s"] = P["s"]; - this["n"] = P["n"] / a; - this["d"] = P["d"] / a; - } else { - return newFraction(P['s'] * P['n'], P['d']); - } -} - -const DivisionByZero = function () { return new Error("Division by Zero"); }; -const InvalidParameter = function () { return new Error("Invalid argument"); }; -const NonIntegerParameter = function () { return new Error("Parameters must be integer"); }; - -Fraction.prototype = { - - "s": C_ONE, - "n": C_ZERO, - "d": C_ONE, - - /** - * Calculates the absolute value - * - * Ex: new Fraction(-4).abs() => 4 - **/ - "abs": function () { - - return newFraction(this["n"], this["d"]); - }, - - /** - * Inverts the sign of the current fraction - * - * Ex: new Fraction(-4).neg() => 4 - **/ - "neg": function () { - - return newFraction(-this["s"] * this["n"], this["d"]); - }, - - /** - * Adds two rational numbers - * - * Ex: new Fraction({n: 2, d: 3}).add("14.9") => 467 / 30 - **/ - "add": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * this["n"] * P["d"] + P["s"] * this["d"] * P["n"], - this["d"] * P["d"] - ); - }, - - /** - * Subtracts two rational numbers - * - * Ex: new Fraction({n: 2, d: 3}).add("14.9") => -427 / 30 - **/ - "sub": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * this["n"] * P["d"] - P["s"] * this["d"] * P["n"], - this["d"] * P["d"] - ); - }, - - /** - * Multiplies two rational numbers - * - * Ex: new Fraction("-17.(345)").mul(3) => 5776 / 111 - **/ - "mul": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * P["s"] * this["n"] * P["n"], - this["d"] * P["d"] - ); - }, - - /** - * Divides two rational numbers - * - * Ex: new Fraction("-17.(345)").inverse().div(3) - **/ - "div": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * P["s"] * this["n"] * P["d"], - this["d"] * P["n"] - ); - }, - - /** - * Clones the actual object - * - * Ex: new Fraction("-17.(345)").clone() - **/ - "clone": function () { - return newFraction(this['s'] * this['n'], this['d']); - }, - - /** - * Calculates the modulo of two rational numbers - a more precise fmod - * - * Ex: new Fraction('4.(3)').mod([7, 8]) => (13/3) % (7/8) = (5/6) - * Ex: new Fraction(20, 10).mod().equals(0) ? "is Integer" - **/ - "mod": function (a, b) { - - if (a === undefined) { - return newFraction(this["s"] * this["n"] % this["d"], C_ONE); - } - - parse(a, b); - if (C_ZERO === P["n"] * this["d"]) { - throw DivisionByZero(); - } - - /** - * I derived the rational modulo similar to the modulo for integers - * - * https://raw.org/book/analysis/rational-numbers/ - * - * n1/d1 = (n2/d2) * q + r, where 0 ≤ r < n2/d2 - * => d2 * n1 = n2 * d1 * q + d1 * d2 * r - * => r = (d2 * n1 - n2 * d1 * q) / (d1 * d2) - * = (d2 * n1 - n2 * d1 * floor((d2 * n1) / (n2 * d1))) / (d1 * d2) - * = ((d2 * n1) % (n2 * d1)) / (d1 * d2) - */ - return newFraction( - this["s"] * (P["d"] * this["n"]) % (P["n"] * this["d"]), - P["d"] * this["d"]); - }, - - /** - * Calculates the fractional gcd of two rational numbers - * - * Ex: new Fraction(5,8).gcd(3,7) => 1/56 - */ - "gcd": function (a, b) { - - parse(a, b); - - // https://raw.org/book/analysis/rational-numbers/ - // gcd(a / b, c / d) = gcd(a, c) / lcm(b, d) - - return newFraction(gcd(P["n"], this["n"]) * gcd(P["d"], this["d"]), P["d"] * this["d"]); - }, - - /** - * Calculates the fractional lcm of two rational numbers - * - * Ex: new Fraction(5,8).lcm(3,7) => 15 - */ - "lcm": function (a, b) { - - parse(a, b); - - // https://raw.org/book/analysis/rational-numbers/ - // lcm(a / b, c / d) = lcm(a, c) / gcd(b, d) - - if (P["n"] === C_ZERO && this["n"] === C_ZERO) { - return newFraction(C_ZERO, C_ONE); - } - return newFraction(P["n"] * this["n"], gcd(P["n"], this["n"]) * gcd(P["d"], this["d"])); - }, - - /** - * Gets the inverse of the fraction, means numerator and denominator are exchanged - * - * Ex: new Fraction([-3, 4]).inverse() => -4 / 3 - **/ - "inverse": function () { - return newFraction(this["s"] * this["d"], this["n"]); - }, - - /** - * Calculates the fraction to some integer exponent - * - * Ex: new Fraction(-1,2).pow(-3) => -8 - */ - "pow": function (a, b) { - - parse(a, b); - - // Trivial case when exp is an integer - - if (P['d'] === C_ONE) { - - if (P['s'] < C_ZERO) { - return newFraction((this['s'] * this["d"]) ** P['n'], this["n"] ** P['n']); - } else { - return newFraction((this['s'] * this["n"]) ** P['n'], this["d"] ** P['n']); - } - } - - // Negative roots become complex - // (-a/b)^(c/d) = x - // ⇔ (-1)^(c/d) * (a/b)^(c/d) = x - // ⇔ (cos(pi) + i*sin(pi))^(c/d) * (a/b)^(c/d) = x - // ⇔ (cos(c*pi/d) + i*sin(c*pi/d)) * (a/b)^(c/d) = x # DeMoivre's formula - // From which follows that only for c=0 the root is non-complex - if (this['s'] < C_ZERO) return null; - - // Now prime factor n and d - let N = factorize(this['n']); - let D = factorize(this['d']); - - // Exponentiate and take root for n and d individually - let n = C_ONE; - let d = C_ONE; - for (let k in N) { - if (k === '1') continue; - if (k === '0') { - n = C_ZERO; - break; - } - N[k] *= P['n']; - - if (N[k] % P['d'] === C_ZERO) { - N[k] /= P['d']; - } else return null; - n *= BigInt(k) ** N[k]; - } - - for (let k in D) { - if (k === '1') continue; - D[k] *= P['n']; - - if (D[k] % P['d'] === C_ZERO) { - D[k] /= P['d']; - } else return null; - d *= BigInt(k) ** D[k]; - } - - if (P['s'] < C_ZERO) { - return newFraction(d, n); - } - return newFraction(n, d); - }, - - /** - * Calculates the logarithm of a fraction to a given rational base - * - * Ex: new Fraction(27, 8).log(9, 4) => 3/2 - */ - "log": function (a, b) { - - parse(a, b); - - if (this['s'] <= C_ZERO || P['s'] <= C_ZERO) return null; - - const allPrimes = Object.create(null); - - const baseFactors = factorize(P['n']); - const T1 = factorize(P['d']); - - const numberFactors = factorize(this['n']); - const T2 = factorize(this['d']); - - for (const prime in T1) { - baseFactors[prime] = (baseFactors[prime] || C_ZERO) - T1[prime]; - } - for (const prime in T2) { - numberFactors[prime] = (numberFactors[prime] || C_ZERO) - T2[prime]; - } - - for (const prime in baseFactors) { - if (prime === '1') continue; - allPrimes[prime] = true; - } - for (const prime in numberFactors) { - if (prime === '1') continue; - allPrimes[prime] = true; - } - - let retN = null; - let retD = null; - - // Iterate over all unique primes to determine if a consistent ratio exists - for (const prime in allPrimes) { - - const baseExponent = baseFactors[prime] || C_ZERO; - const numberExponent = numberFactors[prime] || C_ZERO; - - if (baseExponent === C_ZERO) { - if (numberExponent !== C_ZERO) { - return null; // Logarithm cannot be expressed as a rational number - } - continue; // Skip this prime since both exponents are zero - } - - // Calculate the ratio of exponents for this prime - let curN = numberExponent; - let curD = baseExponent; - - // Simplify the current ratio - const gcdValue = gcd(curN, curD); - curN /= gcdValue; - curD /= gcdValue; - - // Check if this is the first ratio; otherwise, ensure ratios are consistent - if (retN === null && retD === null) { - retN = curN; - retD = curD; - } else if (curN * retD !== retN * curD) { - return null; // Ratios do not match, logarithm cannot be rational - } - } - - return retN !== null && retD !== null - ? newFraction(retN, retD) - : null; - }, - - /** - * Check if two rational numbers are the same - * - * Ex: new Fraction(19.6).equals([98, 5]); - **/ - "equals": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] === P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is less than another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "lt": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] < P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is less than or equal another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "lte": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] <= P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is greater than another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "gt": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] > P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is greater than or equal another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "gte": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] >= P["s"] * P["n"] * this["d"]; - }, - - /** - * Compare two rational numbers - * < 0 iff this < that - * > 0 iff this > that - * = 0 iff this = that - * - * Ex: new Fraction(19.6).compare([98, 5]); - **/ - "compare": function (a, b) { - - parse(a, b); - let t = this["s"] * this["n"] * P["d"] - P["s"] * P["n"] * this["d"]; - - return (C_ZERO < t) - (t < C_ZERO); - }, - - /** - * Calculates the ceil of a rational number - * - * Ex: new Fraction('4.(3)').ceil() => (5 / 1) - **/ - "ceil": function (places) { - - places = C_TEN ** BigInt(places || 0); - - return newFraction(ifloor(this["s"] * places * this["n"] / this["d"]) + - (places * this["n"] % this["d"] > C_ZERO && this["s"] >= C_ZERO ? C_ONE : C_ZERO), - places); - }, - - /** - * Calculates the floor of a rational number - * - * Ex: new Fraction('4.(3)').floor() => (4 / 1) - **/ - "floor": function (places) { - - places = C_TEN ** BigInt(places || 0); - - return newFraction(ifloor(this["s"] * places * this["n"] / this["d"]) - - (places * this["n"] % this["d"] > C_ZERO && this["s"] < C_ZERO ? C_ONE : C_ZERO), - places); - }, - - /** - * Rounds a rational numbers - * - * Ex: new Fraction('4.(3)').round() => (4 / 1) - **/ - "round": function (places) { - - places = C_TEN ** BigInt(places || 0); - - /* Derivation: - - s >= 0: - round(n / d) = ifloor(n / d) + (n % d) / d >= 0.5 ? 1 : 0 - = ifloor(n / d) + 2(n % d) >= d ? 1 : 0 - s < 0: - round(n / d) =-ifloor(n / d) - (n % d) / d > 0.5 ? 1 : 0 - =-ifloor(n / d) - 2(n % d) > d ? 1 : 0 - - =>: - - round(s * n / d) = s * ifloor(n / d) + s * (C + 2(n % d) > d ? 1 : 0) - where C = s >= 0 ? 1 : 0, to fix the >= for the positve case. - */ - - return newFraction(ifloor(this["s"] * places * this["n"] / this["d"]) + - this["s"] * ((this["s"] >= C_ZERO ? C_ONE : C_ZERO) + C_TWO * (places * this["n"] % this["d"]) > this["d"] ? C_ONE : C_ZERO), - places); - }, - - /** - * Rounds a rational number to a multiple of another rational number - * - * Ex: new Fraction('0.9').roundTo("1/8") => 7 / 8 - **/ - "roundTo": function (a, b) { - - /* - k * x/y ≤ a/b < (k+1) * x/y - ⇔ k ≤ a/b / (x/y) < (k+1) - ⇔ k = floor(a/b * y/x) - ⇔ k = floor((a * y) / (b * x)) - */ - - parse(a, b); - - const n = this['n'] * P['d']; - const d = this['d'] * P['n']; - const r = n % d; - - // round(n / d) = ifloor(n / d) + 2(n % d) >= d ? 1 : 0 - let k = ifloor(n / d); - if (r + r >= d) { - k++; - } - return newFraction(this['s'] * k * P['n'], P['d']); - }, - - /** - * Check if two rational numbers are divisible - * - * Ex: new Fraction(19.6).divisible(1.5); - */ - "divisible": function (a, b) { - - parse(a, b); - if (P['n'] === C_ZERO) return false; - return (this['n'] * P['d']) % (P['n'] * this['d']) === C_ZERO; - }, - - /** - * Returns a decimal representation of the fraction - * - * Ex: new Fraction("100.'91823'").valueOf() => 100.91823918239183 - **/ - 'valueOf': function () { - //if (this['n'] <= MAX_INTEGER && this['d'] <= MAX_INTEGER) { - return Number(this['s'] * this['n']) / Number(this['d']); - //} - }, - - /** - * Creates a string representation of a fraction with all digits - * - * Ex: new Fraction("100.'91823'").toString() => "100.(91823)" - **/ - 'toString': function (dec = 15) { - - let N = this["n"]; - let D = this["d"]; - - let cycLen = cycleLen(N, D); // Cycle length - let cycOff = cycleStart(N, D, cycLen); // Cycle start - - let str = this['s'] < C_ZERO ? "-" : ""; - - // Append integer part - str += ifloor(N / D); - - N %= D; - N *= C_TEN; - - if (N) - str += "."; - - if (cycLen) { - - for (let i = cycOff; i--;) { - str += ifloor(N / D); - N %= D; - N *= C_TEN; - } - str += "("; - for (let i = cycLen; i--;) { - str += ifloor(N / D); - N %= D; - N *= C_TEN; - } - str += ")"; - } else { - for (let i = dec; N && i--;) { - str += ifloor(N / D); - N %= D; - N *= C_TEN; - } - } - return str; - }, - - /** - * Returns a string-fraction representation of a Fraction object - * - * Ex: new Fraction("1.'3'").toFraction() => "4 1/3" - **/ - 'toFraction': function (showMixed = false) { - - let n = this["n"]; - let d = this["d"]; - let str = this['s'] < C_ZERO ? "-" : ""; - - if (d === C_ONE) { - str += n; - } else { - const whole = ifloor(n / d); - if (showMixed && whole > C_ZERO) { - str += whole; - str += " "; - n %= d; - } - - str += n; - str += '/'; - str += d; - } - return str; - }, - - /** - * Returns a latex representation of a Fraction object - * - * Ex: new Fraction("1.'3'").toLatex() => "\frac{4}{3}" - **/ - 'toLatex': function (showMixed = false) { - - let n = this["n"]; - let d = this["d"]; - let str = this['s'] < C_ZERO ? "-" : ""; - - if (d === C_ONE) { - str += n; - } else { - const whole = ifloor(n / d); - if (showMixed && whole > C_ZERO) { - str += whole; - n %= d; - } - - str += "\\frac{"; - str += n; - str += '}{'; - str += d; - str += '}'; - } - return str; - }, - - /** - * Returns an array of continued fraction elements - * - * Ex: new Fraction("7/8").toContinued() => [0,1,7] - */ - 'toContinued': function () { - - let a = this['n']; - let b = this['d']; - const res = []; - - while (b) { - res.push(ifloor(a / b)); - const t = a % b; - a = b; - b = t; - } - return res; - }, - - "simplify": function (eps = 1e-3) { - - // Continued fractions give best approximations for a max denominator, - // generally outperforming mediants in denominator–accuracy trade-offs. - // Semiconvergents can further reduce the denominator within tolerance. - - const ieps = BigInt(Math.ceil(1 / eps)); - - const thisABS = this['abs'](); - const cont = thisABS['toContinued'](); - - for (let i = 1; i < cont.length; i++) { - - let s = newFraction(cont[i - 1], C_ONE); - for (let k = i - 2; k >= 0; k--) { - s = s['inverse']()['add'](cont[k]); - } - - let t = s['sub'](thisABS); - if (t['n'] * ieps < t['d']) { // More robust than Math.abs(t.valueOf()) < eps - return s['mul'](this['s']); - } - } - return this; - } -}; - -Object.defineProperty(Fraction, "__esModule", { 'value': true }); -Fraction['default'] = Fraction; -Fraction['Fraction'] = Fraction; -module['exports'] = Fraction; diff --git a/dist/fraction.min.js b/dist/fraction.min.js deleted file mode 100644 index 97b02ee..0000000 --- a/dist/fraction.min.js +++ /dev/null @@ -1,21 +0,0 @@ -/* -Fraction.js v5.3.4 8/22/2025 -https://raw.org/article/rational-numbers-in-javascript/ - -Copyright (c) 2025, Robert Eisele (https://raw.org/) -Licensed under the MIT license. -*/ -'use strict';(function(F){function D(){return Error("Parameters must be integer")}function x(){return Error("Invalid argument")}function C(){return Error("Division by Zero")}function q(a,b){var d=g,c=h;let f=h;if(void 0!==a&&null!==a)if(void 0!==b){if("bigint"===typeof a)d=a;else{if(isNaN(a))throw x();if(0!==a%1)throw D();d=BigInt(a)}if("bigint"===typeof b)c=b;else{if(isNaN(b))throw x();if(0!==b%1)throw D();c=BigInt(b)}f=d*c}else if("object"===typeof a){if("d"in a&&"n"in a)d=BigInt(a.n),c=BigInt(a.d), -"s"in a&&(d*=BigInt(a.s));else if(0 in a)d=BigInt(a[0]),1 in a&&(c=BigInt(a[1]));else if("bigint"===typeof a)d=a;else throw x();f=d*c}else if("number"===typeof a){if(isNaN(a))throw x();0>a&&(f=-h,a=-a);if(0===a%1)d=BigInt(a);else{b=1;var k=0,l=1,m=1;let r=1;1<=a&&(b=10**Math.floor(1+Math.log10(a)),a/=b);for(;1E7>=l&&1E7>=r;)if(c=(k+m)/(l+r),a===c){1E7>=l+r?(d=k+m,c=l+r):r>l?(d=m,c=r):(d=k,c=l);break}else a>c?(k+=m,l+=r):(m+=k,r+=l),1E7h&&(b[a]=(b[a]||g)+h);return b}function y(a,b){if(!a)return b;if(!b)return a;for(;;){a%=b;if(!a)return b;b%=a;if(!b)return a}}function v(a,b){q(a,b);if(this instanceof v)a=y(e.d,e.n),this.s=e.s,this.n=e.n/a,this.d=e.d/a;else return n(e.s*e.n,e.d)}"undefined"===typeof BigInt&& -(BigInt=function(a){if(isNaN(a))throw Error("");return a});const g=BigInt(0),h=BigInt(1),p=BigInt(2),B=BigInt(3),z=BigInt(5),t=BigInt(10),e={s:h,n:g,d:h},G=[p*p,p,p*p,p,p*p,p*B,p,p*B];v.prototype={s:h,n:g,d:h,abs:function(){return n(this.n,this.d)},neg:function(){return n(-this.s*this.n,this.d)},add:function(a,b){q(a,b);return n(this.s*this.n*e.d+e.s*this.d*e.n,this.d*e.d)},sub:function(a,b){q(a,b);return n(this.s*this.n*e.d-e.s*this.d*e.n,this.d*e.d)},mul:function(a,b){q(a,b);return n(this.s*e.s* -this.n*e.n,this.d*e.d)},div:function(a,b){q(a,b);return n(this.s*e.s*this.n*e.d,this.d*e.n)},clone:function(){return n(this.s*this.n,this.d)},mod:function(a,b){if(void 0===a)return n(this.s*this.n%this.d,h);q(a,b);if(g===e.n*this.d)throw C();return n(this.s*e.d*this.n%(e.n*this.d),e.d*this.d)},gcd:function(a,b){q(a,b);return n(y(e.n,this.n)*y(e.d,this.d),e.d*this.d)},lcm:function(a,b){q(a,b);return e.n===g&&this.n===g?n(g,h):n(e.n*this.n,y(e.n,this.n)*y(e.d,this.d))},inverse:function(){return n(this.s* -this.d,this.n)},pow:function(a,b){q(a,b);if(e.d===h)return e.se.s*e.n*this.d},gte:function(a,b){q(a,b);return this.s*this.n*e.d>=e.s*e.n*this.d},compare:function(a,b){q(a,b);a=this.s*this.n*e.d-e.s*e.n*this.d;return(gg&&this.s>=g?h:g),a)},floor:function(a){a=t**BigInt(a||0);return n(u(this.s*a*this.n/ -this.d)-(a*this.n%this.d>g&&this.s=g?h:g)+a*this.n%this.d*p>this.d?h:g),a)},roundTo:function(a,b){q(a,b);var d=this.n*e.d;a=this.d*e.n;b=d%a;d=u(d/a);b+b>=a&&d++;return n(this.s*d*e.n,e.d)},divisible:function(a,b){q(a,b);return e.n===g?!1:this.n*e.d%(e.n*this.d)===g},valueOf:function(){return Number(this.s*this.n)/Number(this.d)},toString:function(a=15){let b=this.n,d=this.d;var c;a:{for(c=d;c%p===g;c/= -p);for(;c%z===g;c/=z);if(c===h)c=g;else{for(var f=t%c,k=1;f!==h;k++)if(f=f*t%c,2E3g;k=k*k%d,l>>=h)l&h&&(m=m*k%d);k=m;for(l=0;300>l;l++){if(f===k){f=BigInt(l);break a}f=f*t%d;k=k*t%d}f=0}k=f;f=this.sg&&(c+=f,c+=" ",b%=d);c=c+b+"/"+d}return c},toLatex:function(a=!1){let b=this.n,d=this.d,c=this.sg&&(c+=f,b%=d);c=c+"\\frac{"+b+"}{"+d;c+="}"}return c},toContinued:function(){let a=this.n,b=this.d;const d=[];for(;b;){d.push(u(a/b));const c=a%b;a=b;b=c}return d},simplify:function(a=.001){a=BigInt(Math.ceil(1/a));const b=this.abs(),d=b.toContinued();for(let f=1;f , 1 => ] - * { n => , d => } - * - * Integer form - * - Single integer value as BigInt or Number - * - * Double form - * - Single double value as Number - * - * String form - * 123.456 - a simple double - * 123/456 - a string fraction - * 123.'456' - a double with repeating decimal places - * 123.(456) - synonym - * 123.45'6' - a double with repeating last place - * 123.45(6) - synonym - * - * Example: - * let f = new Fraction("9.4'31'"); - * f.mul([-4, 3]).div(4.9); - * - */ - -// Set Identity function to downgrade BigInt to Number if needed -if (typeof BigInt === 'undefined') BigInt = function (n) { if (isNaN(n)) throw new Error(""); return n; }; - -const C_ZERO = BigInt(0); -const C_ONE = BigInt(1); -const C_TWO = BigInt(2); -const C_THREE = BigInt(3); -const C_FIVE = BigInt(5); -const C_TEN = BigInt(10); -const MAX_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); - -// Maximum search depth for cyclic rational numbers. 2000 should be more than enough. -// Example: 1/7 = 0.(142857) has 6 repeating decimal places. -// If MAX_CYCLE_LEN gets reduced, long cycles will not be detected and toString() only gets the first 10 digits -const MAX_CYCLE_LEN = 2000; - -// Parsed data to avoid calling "new" all the time -const P = { - "s": C_ONE, - "n": C_ZERO, - "d": C_ONE -}; - -function assign(n, s) { - - try { - n = BigInt(n); - } catch (e) { - throw InvalidParameter(); - } - return n * s; -} - -function ifloor(x) { - return typeof x === 'bigint' ? x : Math.floor(x); -} - -// Creates a new Fraction internally without the need of the bulky constructor -function newFraction(n, d) { - - if (d === C_ZERO) { - throw DivisionByZero(); - } - - const f = Object.create(Fraction.prototype); - f["s"] = n < C_ZERO ? -C_ONE : C_ONE; - - n = n < C_ZERO ? -n : n; - - const a = gcd(n, d); - - f["n"] = n / a; - f["d"] = d / a; - return f; -} - -const FACTORSTEPS = [C_TWO * C_TWO, C_TWO, C_TWO * C_TWO, C_TWO, C_TWO * C_TWO, C_TWO * C_THREE, C_TWO, C_TWO * C_THREE]; // repeats -function factorize(n) { - - const factors = Object.create(null); - if (n <= C_ONE) { - factors[n] = C_ONE; - return factors; - } - - const add = (p) => { factors[p] = (factors[p] || C_ZERO) + C_ONE; }; - - while (n % C_TWO === C_ZERO) { add(C_TWO); n /= C_TWO; } - while (n % C_THREE === C_ZERO) { add(C_THREE); n /= C_THREE; } - while (n % C_FIVE === C_ZERO) { add(C_FIVE); n /= C_FIVE; } - - // 30-wheel trial division: test only residues coprime to 2*3*5 - // Residue step pattern after 5: 7,11,13,17,19,23,29,31, ... - for (let si = 0, p = C_TWO + C_FIVE; p * p <= n;) { - while (n % p === C_ZERO) { add(p); n /= p; } - p += FACTORSTEPS[si]; - si = (si + 1) & 7; // fast modulo 8 - } - if (n > C_ONE) add(n); - return factors; -} - -const parse = function (p1, p2) { - - let n = C_ZERO, d = C_ONE, s = C_ONE; - - if (p1 === undefined || p1 === null) { // No argument - /* void */ - } else if (p2 !== undefined) { // Two arguments - - if (typeof p1 === "bigint") { - n = p1; - } else if (isNaN(p1)) { - throw InvalidParameter(); - } else if (p1 % 1 !== 0) { - throw NonIntegerParameter(); - } else { - n = BigInt(p1); - } - - if (typeof p2 === "bigint") { - d = p2; - } else if (isNaN(p2)) { - throw InvalidParameter(); - } else if (p2 % 1 !== 0) { - throw NonIntegerParameter(); - } else { - d = BigInt(p2); - } - - s = n * d; - - } else if (typeof p1 === "object") { - if ("d" in p1 && "n" in p1) { - n = BigInt(p1["n"]); - d = BigInt(p1["d"]); - if ("s" in p1) - n *= BigInt(p1["s"]); - } else if (0 in p1) { - n = BigInt(p1[0]); - if (1 in p1) - d = BigInt(p1[1]); - } else if (typeof p1 === "bigint") { - n = p1; - } else { - throw InvalidParameter(); - } - s = n * d; - } else if (typeof p1 === "number") { - - if (isNaN(p1)) { - throw InvalidParameter(); - } - - if (p1 < 0) { - s = -C_ONE; - p1 = -p1; - } - - if (p1 % 1 === 0) { - n = BigInt(p1); - } else { - - let z = 1; - - let A = 0, B = 1; - let C = 1, D = 1; - - let N = 10000000; - - if (p1 >= 1) { - z = 10 ** Math.floor(1 + Math.log10(p1)); - p1 /= z; - } - - // Using Farey Sequences - - while (B <= N && D <= N) { - let M = (A + C) / (B + D); - - if (p1 === M) { - if (B + D <= N) { - n = A + C; - d = B + D; - } else if (D > B) { - n = C; - d = D; - } else { - n = A; - d = B; - } - break; - - } else { - - if (p1 > M) { - A += C; - B += D; - } else { - C += A; - D += B; - } - - if (B > N) { - n = C; - d = D; - } else { - n = A; - d = B; - } - } - } - n = BigInt(n) * BigInt(z); - d = BigInt(d); - } - - } else if (typeof p1 === "string") { - - let ndx = 0; - - let v = C_ZERO, w = C_ZERO, x = C_ZERO, y = C_ONE, z = C_ONE; - - let match = p1.replace(/_/g, '').match(/\d+|./g); - - if (match === null) - throw InvalidParameter(); - - if (match[ndx] === '-') {// Check for minus sign at the beginning - s = -C_ONE; - ndx++; - } else if (match[ndx] === '+') {// Check for plus sign at the beginning - ndx++; - } - - if (match.length === ndx + 1) { // Check if it's just a simple number "1234" - w = assign(match[ndx++], s); - } else if (match[ndx + 1] === '.' || match[ndx] === '.') { // Check if it's a decimal number - - if (match[ndx] !== '.') { // Handle 0.5 and .5 - v = assign(match[ndx++], s); - } - ndx++; - - // Check for decimal places - if (ndx + 1 === match.length || match[ndx + 1] === '(' && match[ndx + 3] === ')' || match[ndx + 1] === "'" && match[ndx + 3] === "'") { - w = assign(match[ndx], s); - y = C_TEN ** BigInt(match[ndx].length); - ndx++; - } - - // Check for repeating places - if (match[ndx] === '(' && match[ndx + 2] === ')' || match[ndx] === "'" && match[ndx + 2] === "'") { - x = assign(match[ndx + 1], s); - z = C_TEN ** BigInt(match[ndx + 1].length) - C_ONE; - ndx += 3; - } - - } else if (match[ndx + 1] === '/' || match[ndx + 1] === ':') { // Check for a simple fraction "123/456" or "123:456" - w = assign(match[ndx], s); - y = assign(match[ndx + 2], C_ONE); - ndx += 3; - } else if (match[ndx + 3] === '/' && match[ndx + 1] === ' ') { // Check for a complex fraction "123 1/2" - v = assign(match[ndx], s); - w = assign(match[ndx + 2], s); - y = assign(match[ndx + 4], C_ONE); - ndx += 5; - } - - if (match.length <= ndx) { // Check for more tokens on the stack - d = y * z; - s = /* void */ - n = x + d * v + z * w; - } else { - throw InvalidParameter(); - } - - } else if (typeof p1 === "bigint") { - n = p1; - s = p1; - d = C_ONE; - } else { - throw InvalidParameter(); - } - - if (d === C_ZERO) { - throw DivisionByZero(); - } - - P["s"] = s < C_ZERO ? -C_ONE : C_ONE; - P["n"] = n < C_ZERO ? -n : n; - P["d"] = d < C_ZERO ? -d : d; -}; - -function modpow(b, e, m) { - - let r = C_ONE; - for (; e > C_ZERO; b = (b * b) % m, e >>= C_ONE) { - - if (e & C_ONE) { - r = (r * b) % m; - } - } - return r; -} - -function cycleLen(n, d) { - - for (; d % C_TWO === C_ZERO; - d /= C_TWO) { - } - - for (; d % C_FIVE === C_ZERO; - d /= C_FIVE) { - } - - if (d === C_ONE) // Catch non-cyclic numbers - return C_ZERO; - - // If we would like to compute really large numbers quicker, we could make use of Fermat's little theorem: - // 10^(d-1) % d == 1 - // However, we don't need such large numbers and MAX_CYCLE_LEN should be the capstone, - // as we want to translate the numbers to strings. - - let rem = C_TEN % d; - let t = 1; - - for (; rem !== C_ONE; t++) { - rem = rem * C_TEN % d; - - if (t > MAX_CYCLE_LEN) - return C_ZERO; // Returning 0 here means that we don't print it as a cyclic number. It's likely that the answer is `d-1` - } - return BigInt(t); -} - -function cycleStart(n, d, len) { - - let rem1 = C_ONE; - let rem2 = modpow(C_TEN, len, d); - - for (let t = 0; t < 300; t++) { // s < ~log10(Number.MAX_VALUE) - // Solve 10^s == 10^(s+t) (mod d) - - if (rem1 === rem2) - return BigInt(t); - - rem1 = rem1 * C_TEN % d; - rem2 = rem2 * C_TEN % d; - } - return 0; -} - -function gcd(a, b) { - - if (!a) - return b; - if (!b) - return a; - - while (1) { - a %= b; - if (!a) - return b; - b %= a; - if (!b) - return a; - } -} - -/** - * Module constructor - * - * @constructor - * @param {number|Fraction=} a - * @param {number=} b - */ -function Fraction(a, b) { - - parse(a, b); - - if (this instanceof Fraction) { - a = gcd(P["d"], P["n"]); // Abuse a - this["s"] = P["s"]; - this["n"] = P["n"] / a; - this["d"] = P["d"] / a; - } else { - return newFraction(P['s'] * P['n'], P['d']); - } -} - -const DivisionByZero = function () { return new Error("Division by Zero"); }; -const InvalidParameter = function () { return new Error("Invalid argument"); }; -const NonIntegerParameter = function () { return new Error("Parameters must be integer"); }; - -Fraction.prototype = { - - "s": C_ONE, - "n": C_ZERO, - "d": C_ONE, - - /** - * Calculates the absolute value - * - * Ex: new Fraction(-4).abs() => 4 - **/ - "abs": function () { - - return newFraction(this["n"], this["d"]); - }, - - /** - * Inverts the sign of the current fraction - * - * Ex: new Fraction(-4).neg() => 4 - **/ - "neg": function () { - - return newFraction(-this["s"] * this["n"], this["d"]); - }, - - /** - * Adds two rational numbers - * - * Ex: new Fraction({n: 2, d: 3}).add("14.9") => 467 / 30 - **/ - "add": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * this["n"] * P["d"] + P["s"] * this["d"] * P["n"], - this["d"] * P["d"] - ); - }, - - /** - * Subtracts two rational numbers - * - * Ex: new Fraction({n: 2, d: 3}).add("14.9") => -427 / 30 - **/ - "sub": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * this["n"] * P["d"] - P["s"] * this["d"] * P["n"], - this["d"] * P["d"] - ); - }, - - /** - * Multiplies two rational numbers - * - * Ex: new Fraction("-17.(345)").mul(3) => 5776 / 111 - **/ - "mul": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * P["s"] * this["n"] * P["n"], - this["d"] * P["d"] - ); - }, - - /** - * Divides two rational numbers - * - * Ex: new Fraction("-17.(345)").inverse().div(3) - **/ - "div": function (a, b) { - - parse(a, b); - return newFraction( - this["s"] * P["s"] * this["n"] * P["d"], - this["d"] * P["n"] - ); - }, - - /** - * Clones the actual object - * - * Ex: new Fraction("-17.(345)").clone() - **/ - "clone": function () { - return newFraction(this['s'] * this['n'], this['d']); - }, - - /** - * Calculates the modulo of two rational numbers - a more precise fmod - * - * Ex: new Fraction('4.(3)').mod([7, 8]) => (13/3) % (7/8) = (5/6) - * Ex: new Fraction(20, 10).mod().equals(0) ? "is Integer" - **/ - "mod": function (a, b) { - - if (a === undefined) { - return newFraction(this["s"] * this["n"] % this["d"], C_ONE); - } - - parse(a, b); - if (C_ZERO === P["n"] * this["d"]) { - throw DivisionByZero(); - } - - /** - * I derived the rational modulo similar to the modulo for integers - * - * https://raw.org/book/analysis/rational-numbers/ - * - * n1/d1 = (n2/d2) * q + r, where 0 ≤ r < n2/d2 - * => d2 * n1 = n2 * d1 * q + d1 * d2 * r - * => r = (d2 * n1 - n2 * d1 * q) / (d1 * d2) - * = (d2 * n1 - n2 * d1 * floor((d2 * n1) / (n2 * d1))) / (d1 * d2) - * = ((d2 * n1) % (n2 * d1)) / (d1 * d2) - */ - return newFraction( - this["s"] * (P["d"] * this["n"]) % (P["n"] * this["d"]), - P["d"] * this["d"]); - }, - - /** - * Calculates the fractional gcd of two rational numbers - * - * Ex: new Fraction(5,8).gcd(3,7) => 1/56 - */ - "gcd": function (a, b) { - - parse(a, b); - - // https://raw.org/book/analysis/rational-numbers/ - // gcd(a / b, c / d) = gcd(a, c) / lcm(b, d) - - return newFraction(gcd(P["n"], this["n"]) * gcd(P["d"], this["d"]), P["d"] * this["d"]); - }, - - /** - * Calculates the fractional lcm of two rational numbers - * - * Ex: new Fraction(5,8).lcm(3,7) => 15 - */ - "lcm": function (a, b) { - - parse(a, b); - - // https://raw.org/book/analysis/rational-numbers/ - // lcm(a / b, c / d) = lcm(a, c) / gcd(b, d) - - if (P["n"] === C_ZERO && this["n"] === C_ZERO) { - return newFraction(C_ZERO, C_ONE); - } - return newFraction(P["n"] * this["n"], gcd(P["n"], this["n"]) * gcd(P["d"], this["d"])); - }, - - /** - * Gets the inverse of the fraction, means numerator and denominator are exchanged - * - * Ex: new Fraction([-3, 4]).inverse() => -4 / 3 - **/ - "inverse": function () { - return newFraction(this["s"] * this["d"], this["n"]); - }, - - /** - * Calculates the fraction to some integer exponent - * - * Ex: new Fraction(-1,2).pow(-3) => -8 - */ - "pow": function (a, b) { - - parse(a, b); - - // Trivial case when exp is an integer - - if (P['d'] === C_ONE) { - - if (P['s'] < C_ZERO) { - return newFraction((this['s'] * this["d"]) ** P['n'], this["n"] ** P['n']); - } else { - return newFraction((this['s'] * this["n"]) ** P['n'], this["d"] ** P['n']); - } - } - - // Negative roots become complex - // (-a/b)^(c/d) = x - // ⇔ (-1)^(c/d) * (a/b)^(c/d) = x - // ⇔ (cos(pi) + i*sin(pi))^(c/d) * (a/b)^(c/d) = x - // ⇔ (cos(c*pi/d) + i*sin(c*pi/d)) * (a/b)^(c/d) = x # DeMoivre's formula - // From which follows that only for c=0 the root is non-complex - if (this['s'] < C_ZERO) return null; - - // Now prime factor n and d - let N = factorize(this['n']); - let D = factorize(this['d']); - - // Exponentiate and take root for n and d individually - let n = C_ONE; - let d = C_ONE; - for (let k in N) { - if (k === '1') continue; - if (k === '0') { - n = C_ZERO; - break; - } - N[k] *= P['n']; - - if (N[k] % P['d'] === C_ZERO) { - N[k] /= P['d']; - } else return null; - n *= BigInt(k) ** N[k]; - } - - for (let k in D) { - if (k === '1') continue; - D[k] *= P['n']; - - if (D[k] % P['d'] === C_ZERO) { - D[k] /= P['d']; - } else return null; - d *= BigInt(k) ** D[k]; - } - - if (P['s'] < C_ZERO) { - return newFraction(d, n); - } - return newFraction(n, d); - }, - - /** - * Calculates the logarithm of a fraction to a given rational base - * - * Ex: new Fraction(27, 8).log(9, 4) => 3/2 - */ - "log": function (a, b) { - - parse(a, b); - - if (this['s'] <= C_ZERO || P['s'] <= C_ZERO) return null; - - const allPrimes = Object.create(null); - - const baseFactors = factorize(P['n']); - const T1 = factorize(P['d']); - - const numberFactors = factorize(this['n']); - const T2 = factorize(this['d']); - - for (const prime in T1) { - baseFactors[prime] = (baseFactors[prime] || C_ZERO) - T1[prime]; - } - for (const prime in T2) { - numberFactors[prime] = (numberFactors[prime] || C_ZERO) - T2[prime]; - } - - for (const prime in baseFactors) { - if (prime === '1') continue; - allPrimes[prime] = true; - } - for (const prime in numberFactors) { - if (prime === '1') continue; - allPrimes[prime] = true; - } - - let retN = null; - let retD = null; - - // Iterate over all unique primes to determine if a consistent ratio exists - for (const prime in allPrimes) { - - const baseExponent = baseFactors[prime] || C_ZERO; - const numberExponent = numberFactors[prime] || C_ZERO; - - if (baseExponent === C_ZERO) { - if (numberExponent !== C_ZERO) { - return null; // Logarithm cannot be expressed as a rational number - } - continue; // Skip this prime since both exponents are zero - } - - // Calculate the ratio of exponents for this prime - let curN = numberExponent; - let curD = baseExponent; - - // Simplify the current ratio - const gcdValue = gcd(curN, curD); - curN /= gcdValue; - curD /= gcdValue; - - // Check if this is the first ratio; otherwise, ensure ratios are consistent - if (retN === null && retD === null) { - retN = curN; - retD = curD; - } else if (curN * retD !== retN * curD) { - return null; // Ratios do not match, logarithm cannot be rational - } - } - - return retN !== null && retD !== null - ? newFraction(retN, retD) - : null; - }, - - /** - * Check if two rational numbers are the same - * - * Ex: new Fraction(19.6).equals([98, 5]); - **/ - "equals": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] === P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is less than another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "lt": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] < P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is less than or equal another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "lte": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] <= P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is greater than another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "gt": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] > P["s"] * P["n"] * this["d"]; - }, - - /** - * Check if this rational number is greater than or equal another - * - * Ex: new Fraction(19.6).lt([98, 5]); - **/ - "gte": function (a, b) { - - parse(a, b); - return this["s"] * this["n"] * P["d"] >= P["s"] * P["n"] * this["d"]; - }, - - /** - * Compare two rational numbers - * < 0 iff this < that - * > 0 iff this > that - * = 0 iff this = that - * - * Ex: new Fraction(19.6).compare([98, 5]); - **/ - "compare": function (a, b) { - - parse(a, b); - let t = this["s"] * this["n"] * P["d"] - P["s"] * P["n"] * this["d"]; - - return (C_ZERO < t) - (t < C_ZERO); - }, - - /** - * Calculates the ceil of a rational number - * - * Ex: new Fraction('4.(3)').ceil() => (5 / 1) - **/ - "ceil": function (places) { - - places = C_TEN ** BigInt(places || 0); - - return newFraction(ifloor(this["s"] * places * this["n"] / this["d"]) + - (places * this["n"] % this["d"] > C_ZERO && this["s"] >= C_ZERO ? C_ONE : C_ZERO), - places); - }, - - /** - * Calculates the floor of a rational number - * - * Ex: new Fraction('4.(3)').floor() => (4 / 1) - **/ - "floor": function (places) { - - places = C_TEN ** BigInt(places || 0); - - return newFraction(ifloor(this["s"] * places * this["n"] / this["d"]) - - (places * this["n"] % this["d"] > C_ZERO && this["s"] < C_ZERO ? C_ONE : C_ZERO), - places); - }, - - /** - * Rounds a rational numbers - * - * Ex: new Fraction('4.(3)').round() => (4 / 1) - **/ - "round": function (places) { - - places = C_TEN ** BigInt(places || 0); - - /* Derivation: - - s >= 0: - round(n / d) = ifloor(n / d) + (n % d) / d >= 0.5 ? 1 : 0 - = ifloor(n / d) + 2(n % d) >= d ? 1 : 0 - s < 0: - round(n / d) =-ifloor(n / d) - (n % d) / d > 0.5 ? 1 : 0 - =-ifloor(n / d) - 2(n % d) > d ? 1 : 0 - - =>: - - round(s * n / d) = s * ifloor(n / d) + s * (C + 2(n % d) > d ? 1 : 0) - where C = s >= 0 ? 1 : 0, to fix the >= for the positve case. - */ - - return newFraction(ifloor(this["s"] * places * this["n"] / this["d"]) + - this["s"] * ((this["s"] >= C_ZERO ? C_ONE : C_ZERO) + C_TWO * (places * this["n"] % this["d"]) > this["d"] ? C_ONE : C_ZERO), - places); - }, - - /** - * Rounds a rational number to a multiple of another rational number - * - * Ex: new Fraction('0.9').roundTo("1/8") => 7 / 8 - **/ - "roundTo": function (a, b) { - - /* - k * x/y ≤ a/b < (k+1) * x/y - ⇔ k ≤ a/b / (x/y) < (k+1) - ⇔ k = floor(a/b * y/x) - ⇔ k = floor((a * y) / (b * x)) - */ - - parse(a, b); - - const n = this['n'] * P['d']; - const d = this['d'] * P['n']; - const r = n % d; - - // round(n / d) = ifloor(n / d) + 2(n % d) >= d ? 1 : 0 - let k = ifloor(n / d); - if (r + r >= d) { - k++; - } - return newFraction(this['s'] * k * P['n'], P['d']); - }, - - /** - * Check if two rational numbers are divisible - * - * Ex: new Fraction(19.6).divisible(1.5); - */ - "divisible": function (a, b) { - - parse(a, b); - if (P['n'] === C_ZERO) return false; - return (this['n'] * P['d']) % (P['n'] * this['d']) === C_ZERO; - }, - - /** - * Returns a decimal representation of the fraction - * - * Ex: new Fraction("100.'91823'").valueOf() => 100.91823918239183 - **/ - 'valueOf': function () { - //if (this['n'] <= MAX_INTEGER && this['d'] <= MAX_INTEGER) { - return Number(this['s'] * this['n']) / Number(this['d']); - //} - }, - - /** - * Creates a string representation of a fraction with all digits - * - * Ex: new Fraction("100.'91823'").toString() => "100.(91823)" - **/ - 'toString': function (dec = 15) { - - let N = this["n"]; - let D = this["d"]; - - let cycLen = cycleLen(N, D); // Cycle length - let cycOff = cycleStart(N, D, cycLen); // Cycle start - - let str = this['s'] < C_ZERO ? "-" : ""; - - // Append integer part - str += ifloor(N / D); - - N %= D; - N *= C_TEN; - - if (N) - str += "."; - - if (cycLen) { - - for (let i = cycOff; i--;) { - str += ifloor(N / D); - N %= D; - N *= C_TEN; - } - str += "("; - for (let i = cycLen; i--;) { - str += ifloor(N / D); - N %= D; - N *= C_TEN; - } - str += ")"; - } else { - for (let i = dec; N && i--;) { - str += ifloor(N / D); - N %= D; - N *= C_TEN; - } - } - return str; - }, - - /** - * Returns a string-fraction representation of a Fraction object - * - * Ex: new Fraction("1.'3'").toFraction() => "4 1/3" - **/ - 'toFraction': function (showMixed = false) { - - let n = this["n"]; - let d = this["d"]; - let str = this['s'] < C_ZERO ? "-" : ""; - - if (d === C_ONE) { - str += n; - } else { - const whole = ifloor(n / d); - if (showMixed && whole > C_ZERO) { - str += whole; - str += " "; - n %= d; - } - - str += n; - str += '/'; - str += d; - } - return str; - }, - - /** - * Returns a latex representation of a Fraction object - * - * Ex: new Fraction("1.'3'").toLatex() => "\frac{4}{3}" - **/ - 'toLatex': function (showMixed = false) { - - let n = this["n"]; - let d = this["d"]; - let str = this['s'] < C_ZERO ? "-" : ""; - - if (d === C_ONE) { - str += n; - } else { - const whole = ifloor(n / d); - if (showMixed && whole > C_ZERO) { - str += whole; - n %= d; - } - - str += "\\frac{"; - str += n; - str += '}{'; - str += d; - str += '}'; - } - return str; - }, - - /** - * Returns an array of continued fraction elements - * - * Ex: new Fraction("7/8").toContinued() => [0,1,7] - */ - 'toContinued': function () { - - let a = this['n']; - let b = this['d']; - const res = []; - - while (b) { - res.push(ifloor(a / b)); - const t = a % b; - a = b; - b = t; - } - return res; - }, - - "simplify": function (eps = 1e-3) { - - // Continued fractions give best approximations for a max denominator, - // generally outperforming mediants in denominator–accuracy trade-offs. - // Semiconvergents can further reduce the denominator within tolerance. - - const ieps = BigInt(Math.ceil(1 / eps)); - - const thisABS = this['abs'](); - const cont = thisABS['toContinued'](); - - for (let i = 1; i < cont.length; i++) { - - let s = newFraction(cont[i - 1], C_ONE); - for (let k = i - 2; k >= 0; k--) { - s = s['inverse']()['add'](cont[k]); - } - - let t = s['sub'](thisABS); - if (t['n'] * ieps < t['d']) { // More robust than Math.abs(t.valueOf()) < eps - return s['mul'](this['s']); - } - } - return this; - } -}; -export { - Fraction as default, Fraction -}; diff --git a/package.json b/package.json index 03f7986..526cbf2 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,12 @@ "example": "examples", "test": "tests" }, + "files": [ + "fraction.d.?(m)ts", + "dist" + ], "scripts": { + "prepublishOnly": "npm run build", "build": "crude-build Fraction", "test": "mocha tests/*.js" }, @@ -78,4 +83,4 @@ "crude-build": "^0.1.2", "mocha": "*" } -} \ No newline at end of file +}