UNPKG

xen-dev-utils

Version:

Utility functions used by the Scale Workshop ecosystem

688 lines 35.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const fraction_1 = require("../fraction"); (0, vitest_1.describe)('gcd', () => { (0, vitest_1.it)('can find the greatest common divisor of 12 and 15 (number)', () => { (0, vitest_1.expect)((0, fraction_1.gcd)(12, 15)).toBe(3); }); (0, vitest_1.it)('can find the greatest common divisor of 12 and 15 (bigint)', () => { (0, vitest_1.expect)((0, fraction_1.gcd)(12n, 15n)).toBe(3n); }); (0, vitest_1.it)('has an identity element (left)', () => { (0, vitest_1.expect)((0, fraction_1.gcd)(12, 0)).toBe(12); }); (0, vitest_1.it)('has an identity element (right)', () => { (0, vitest_1.expect)((0, fraction_1.gcd)(0, 12)).toBe(12); }); (0, vitest_1.it)('has an identity element (self)', () => { (0, vitest_1.expect)((0, fraction_1.gcd)(0, 0)).toBe(0); }); (0, vitest_1.it)('has an identity element (bigint)', () => { (0, vitest_1.expect)((0, fraction_1.gcd)(12n, 0n)).toBe(12n); }); }); (0, vitest_1.describe)('lcm', () => { (0, vitest_1.it)('can find the least common multiple of 6 and 14 (number)', () => { (0, vitest_1.expect)((0, fraction_1.lcm)(6, 14)).toBe(42); }); (0, vitest_1.it)('can find the least common multiple of 6 and 14 (bigint)', () => { (0, vitest_1.expect)((0, fraction_1.lcm)(6n, 14n)).toBe(42n); }); (0, vitest_1.it)('works with zero (left)', () => { (0, vitest_1.expect)((0, fraction_1.lcm)(0, 12)).toBe(0); }); (0, vitest_1.it)('works with zero (right)', () => { (0, vitest_1.expect)((0, fraction_1.lcm)(12, 0)).toBe(0); }); (0, vitest_1.it)('works with zero (both)', () => { (0, vitest_1.expect)((0, fraction_1.lcm)(0, 0)).toBe(0); }); (0, vitest_1.it)('works with zero (bigint)', () => { (0, vitest_1.expect)((0, fraction_1.lcm)(0n, 12n)).toBe(0n); }); }); (0, vitest_1.describe)('gcd with lcm', () => { (0, vitest_1.it)('satisfies the identity for small integers', () => { for (let i = -10; i <= 10; ++i) { for (let j = -10; j <= 10; ++j) { // We need to bypass (+0).toBe(-0) here... (0, vitest_1.expect)((0, fraction_1.gcd)(i, j) * (0, fraction_1.lcm)(i, j) === i * j, `failed with ${i}, ${j}`).toBe(true); // This works, though. const x = BigInt(i); const y = BigInt(j); (0, vitest_1.expect)((0, fraction_1.gcd)(x, y) * (0, fraction_1.lcm)(x, y)).toBe(x * y); } } }); }); (0, vitest_1.describe)('mmod', () => { (0, vitest_1.it)('works with negative numbers (number)', () => { (0, vitest_1.expect)((0, fraction_1.mmod)(-5, 3)).toBe(1); }); (0, vitest_1.it)('works with negative numbers (bigint)', () => { (0, vitest_1.expect)((0, fraction_1.mmod)(-5n, 3n)).toBe(1n); }); (0, vitest_1.it)('produces NaN for 1 % 0', () => { (0, vitest_1.expect)((0, fraction_1.mmod)(1, 0)).toBeNaN(); }); (0, vitest_1.it)('throws for 1n % 0n', () => { (0, vitest_1.expect)(() => (0, fraction_1.mmod)(1n, 0n)).toThrow(); }); }); (0, vitest_1.describe)('Ceiling modulo', () => { (0, vitest_1.it)('works like clockwork', () => { (0, vitest_1.expect)([...Array(13).keys()].map(i => (0, fraction_1.modc)(i, 12))).toEqual([ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ]); }); (0, vitest_1.it)('works with negative numbers (bigint)', () => { (0, vitest_1.expect)((0, fraction_1.modc)(-5n, 3n)).toBe(1n); }); (0, vitest_1.it)('produces 0 for 1 % 0', () => { (0, vitest_1.expect)((0, fraction_1.modc)(1, 0)).toBe(0); }); (0, vitest_1.it)('produces 0n for 1n % 0n', () => { (0, vitest_1.expect)((0, fraction_1.modc)(1n, 0n)).toBe(0n); }); }); (0, vitest_1.describe)('Fraction', () => { (0, vitest_1.it)('can be constructed from numerator and denominator', () => { const fraction = new fraction_1.Fraction(-6, -12); (0, vitest_1.expect)(fraction.s).toBe(1); (0, vitest_1.expect)(fraction.n).toBe(1); (0, vitest_1.expect)(fraction.d).toBe(2); }); (0, vitest_1.it)('can be constructed from a floating point number', () => { const fraction = new fraction_1.Fraction(-0.875); (0, vitest_1.expect)(fraction.s).toBe(-1); (0, vitest_1.expect)(fraction.n).toBe(7); (0, vitest_1.expect)(fraction.d).toBe(8); }); (0, vitest_1.it)('can be constructed from a plain number string', () => { const fraction = new fraction_1.Fraction('5'); (0, vitest_1.expect)(fraction.s).toBe(1); (0, vitest_1.expect)(fraction.n).toBe(5); (0, vitest_1.expect)(fraction.d).toBe(1); }); (0, vitest_1.it)('can be constructed from a decimal string', () => { const fraction = new fraction_1.Fraction('3.14159'); (0, vitest_1.expect)(fraction.s).toBe(1); (0, vitest_1.expect)(fraction.n).toBe(314159); (0, vitest_1.expect)(fraction.d).toBe(100000); }); (0, vitest_1.it)('can be construction from a fraction string', () => { const fraction = new fraction_1.Fraction('-9/12'); (0, vitest_1.expect)(fraction.s).toBe(-1); (0, vitest_1.expect)(fraction.n).toBe(3); (0, vitest_1.expect)(fraction.d).toBe(4); }); (0, vitest_1.it)('can calculate the square root of 36/25', () => { const fraction = new fraction_1.Fraction(36, 25); const half = new fraction_1.Fraction(1, 2); const result = fraction.pow(half); (0, vitest_1.expect)(result).not.toBeNull(); (0, vitest_1.expect)(result.s).toBe(1); (0, vitest_1.expect)(result.n).toBe(6); (0, vitest_1.expect)(result.d).toBe(5); }); (0, vitest_1.it)('can calculate the inverse square root of 36/25', () => { const fraction = new fraction_1.Fraction(36, 25); const negHalf = new fraction_1.Fraction(-1, 2); const result = fraction.pow(negHalf); (0, vitest_1.expect)(result).not.toBeNull(); (0, vitest_1.expect)(result.s).toBe(1); (0, vitest_1.expect)(result.n).toBe(5); (0, vitest_1.expect)(result.d).toBe(6); }); (0, vitest_1.it)('can calculate (-125/27) ** (1/3)', () => { const fraction = new fraction_1.Fraction(-125, 27); const result = fraction.pow('1/3'); (0, vitest_1.expect)(result).not.toBeNull(); (0, vitest_1.expect)(result.s).toBe(-1); (0, vitest_1.expect)(result.n).toBe(5); (0, vitest_1.expect)(result.d).toBe(3); }); (0, vitest_1.it)('can calculate (-125/27) ** (2/3)', () => { const fraction = new fraction_1.Fraction(-125, 27); const result = fraction.pow('2/3'); (0, vitest_1.expect)(result).not.toBeNull(); (0, vitest_1.expect)(result.s).toBe(1); (0, vitest_1.expect)(result.n).toBe(25); (0, vitest_1.expect)(result.d).toBe(9); }); (0, vitest_1.it)('infers zeroes for decimal components', () => { const half = new fraction_1.Fraction('.5'); (0, vitest_1.expect)(half.valueOf()).toBe(0.5); const negativeQuarter = new fraction_1.Fraction('-.25'); (0, vitest_1.expect)(negativeQuarter.valueOf()).toBe(-0.25); const two = new fraction_1.Fraction('2.'); (0, vitest_1.expect)(two.valueOf()).toBe(2); const zero = new fraction_1.Fraction('.'); (0, vitest_1.expect)(zero.valueOf()).toBe(0); }); (0, vitest_1.it)('infers ones for slash components', () => { const third = new fraction_1.Fraction('/3'); (0, vitest_1.expect)(third.s).toBe(1); (0, vitest_1.expect)(third.n).toBe(1); (0, vitest_1.expect)(third.d).toBe(3); const fifth = new fraction_1.Fraction('-/5'); (0, vitest_1.expect)(fifth.s).toBe(-1); (0, vitest_1.expect)(fifth.n).toBe(1); (0, vitest_1.expect)(fifth.d).toBe(5); const four = new fraction_1.Fraction('-4/'); (0, vitest_1.expect)(four.s).toBe(-1); (0, vitest_1.expect)(four.n).toBe(4); (0, vitest_1.expect)(four.d).toBe(1); const one = new fraction_1.Fraction('/'); (0, vitest_1.expect)(one.s).toBe(1); (0, vitest_1.expect)(one.n).toBe(1); (0, vitest_1.expect)(one.d).toBe(1); }); (0, vitest_1.it)('supports scientific notation', () => { const thirtySevenPercent = new fraction_1.Fraction('37e-2'); (0, vitest_1.expect)(thirtySevenPercent.s).toBe(1); (0, vitest_1.expect)(thirtySevenPercent.n).toBe(37); (0, vitest_1.expect)(thirtySevenPercent.d).toBe(100); const minusTwelve = new fraction_1.Fraction('-1.2e1'); (0, vitest_1.expect)(minusTwelve.s).toBe(-1); (0, vitest_1.expect)(minusTwelve.n).toBe(12); (0, vitest_1.expect)(minusTwelve.d).toBe(1); const cursedTritone = new fraction_1.Fraction('14e-1'); (0, vitest_1.expect)(cursedTritone.s).toBe(1); (0, vitest_1.expect)(cursedTritone.n).toBe(7); (0, vitest_1.expect)(cursedTritone.d).toBe(5); const pleaseDont = new fraction_1.Fraction('-11/3e2'); (0, vitest_1.expect)(pleaseDont.s).toBe(-1); (0, vitest_1.expect)(pleaseDont.n).toBe(1100); (0, vitest_1.expect)(pleaseDont.d).toBe(3); }); // These obviously crashes the engine before failing the test. No way around that. (0, vitest_1.it)('produces a finite continued fraction from a random value (0, 10)', () => { const value = Math.random() * 10; const fraction = new fraction_1.Fraction(value); (0, vitest_1.expect)(fraction.toContinued().length).toBeLessThan(Infinity); }); vitest_1.it.skip('produces a finite continued fraction from a random value (MAX_SAFE, 1e20<<', () => { const value = Number.MAX_SAFE_INTEGER + Math.random() * 1e19; const fraction = new fraction_1.Fraction(value); // This obviously crashes the engine before failing the test. No way around that. (0, vitest_1.expect)(fraction.toContinued().length).toBeLessThan(Infinity); }); vitest_1.it.skip('produces a finite continued fraction from a balanced high complexity value', () => { const fraction = new fraction_1.Fraction(Number.MAX_SAFE_INTEGER + Math.random() * 1e18, Number.MAX_SAFE_INTEGER + Math.random() * 1e18); (0, vitest_1.expect)(fraction.toContinued().length).toBeLessThan(Infinity); }); vitest_1.it.skip('produces a finite continued fraction from an imbalanced high complexity value', () => { const fraction = new fraction_1.Fraction(Math.floor(Math.random() * 1000), Number.MAX_SAFE_INTEGER + Math.random() * 1e18); (0, vitest_1.expect)(fraction.toContinued().length).toBeLessThan(Infinity); }); vitest_1.it.skip('produces a finite continued fraction from infinity', () => { const fraction = new fraction_1.Fraction(Infinity); (0, vitest_1.expect)(fraction.toContinued().length).toBeLessThan(Infinity); }); (0, vitest_1.it)('can approximate the golden ratio', () => { let approximant = new fraction_1.Fraction(1); for (let i = 0; i < 76; ++i) { approximant = approximant.inverse().add(1); // Finite numbers have two valid representations. // This is the shorter one. const expected = Array(i).fill(1); expected.push(2); (0, vitest_1.expect)(approximant.toContinued()).toEqual(expected); } }); (0, vitest_1.it)('can simplify a random number using an absolute metric', () => { const value = Math.random() * 4 - 2; const fraction = new fraction_1.Fraction(value); (0, vitest_1.expect)(fraction.simplify().valueOf()).toBeCloseTo(value); }); (0, vitest_1.it)('can simplify a random number using a relative metric', () => { const value = Math.exp(Math.random() * 20 - 10) * (Math.floor(2 * Math.random()) * 2 - 1); const fraction = new fraction_1.Fraction(value); const simplified = fraction.simplifyRelative().valueOf(); (0, vitest_1.expect)(Math.sign(simplified)).toBe(Math.sign(value)); (0, vitest_1.expect)(Math.abs(Math.log(Math.abs(simplified)) - Math.log(Math.abs(value)))).toBeLessThanOrEqual((Math.LN2 / 1200) * 3.5); }); (0, vitest_1.it)('can parse a repeated decimal', () => { const fraction = new fraction_1.Fraction("3.'3'"); (0, vitest_1.expect)(fraction.s).toBe(1); (0, vitest_1.expect)(fraction.n).toBe(10); (0, vitest_1.expect)(fraction.d).toBe(3); }); (0, vitest_1.it)('can parse a repeated decimal (zero whole part)', () => { const fraction = new fraction_1.Fraction("0.'1'"); (0, vitest_1.expect)(fraction.s).toBe(1); (0, vitest_1.expect)(fraction.n).toBe(1); (0, vitest_1.expect)(fraction.d).toBe(9); }); // Need BigInt for this. vitest_1.it.skip('can parse repeated decimal (late cycle)', () => { const fraction = new fraction_1.Fraction("0.269'736842105263157894'"); console.log(fraction); }); (0, vitest_1.it)('can produce repeated decimals', () => { const fraction = new fraction_1.Fraction(5, 11); (0, vitest_1.expect)(fraction.toString()).toBe("0.'45'"); }); (0, vitest_1.it)('is not equal to NaN', () => { const fraction = new fraction_1.Fraction(3, 2); (0, vitest_1.expect)(fraction.equals(NaN)).toBe(false); }); (0, vitest_1.it)('is not equal to garbage', () => { const fraction = new fraction_1.Fraction(3, 2); (0, vitest_1.expect)(fraction.equals('asdf')).toBe(false); }); (0, vitest_1.it)("doesn't compare to NaN", () => { const fraction = new fraction_1.Fraction(7, 3); (0, vitest_1.expect)(fraction.compare(NaN)).toBeNaN(); }); (0, vitest_1.it)("doesn't compare to garbage", () => { const fraction = new fraction_1.Fraction(7, 3); (0, vitest_1.expect)(fraction.compare('garbage')).toBeNaN(); }); (0, vitest_1.it)('is not divisible by NaN', () => { const fraction = new fraction_1.Fraction(13, 11); (0, vitest_1.expect)(fraction.divisible(NaN)).toBe(false); }); (0, vitest_1.it)('is not divisible by garbage', () => { const fraction = new fraction_1.Fraction(13, 11); (0, vitest_1.expect)(fraction.divisible('conquer')).toBe(false); }); (0, vitest_1.it)('gives the correct error for too large components', () => { (0, vitest_1.expect)(() => new fraction_1.Fraction(1.1231233477899796e16, 1)).toThrowError('Numerator above safe limit'); }); (0, vitest_1.it)('can convert a problematic float to a fraction', () => { const x = 0.5717619047619048; const y = new fraction_1.Fraction(x); (0, vitest_1.expect)(y.valueOf()).toBeCloseTo(x); }); (0, vitest_1.it)('has a geometric modulo (integers)', () => { const fraction = new fraction_1.Fraction(5); (0, vitest_1.expect)(fraction.geoMod(2).equals('5/4')).toBe(true); }); (0, vitest_1.it)('has a geometric modulo (fractions)', () => { const fraction = new fraction_1.Fraction(19, 5); (0, vitest_1.expect)(fraction.geoMod('3/2').equals('152/135')).toBe(true); }); (0, vitest_1.it)('has a geometric modulo (sub-unity)', () => { const fraction = new fraction_1.Fraction(7); (0, vitest_1.expect)(fraction.geoMod('1/2').equals('7/8')).toBe(true); }); (0, vitest_1.it)('has a geometric modulo (negative numbers)', () => { const fraction = new fraction_1.Fraction(11); (0, vitest_1.expect)(fraction.geoMod(-2).equals('-11/8')).toBe(true); }); (0, vitest_1.it)('has a geometric modulo (unity)', () => { const fraction = new fraction_1.Fraction(1); (0, vitest_1.expect)(fraction.geoMod(3).equals(1)).toBe(true); }); (0, vitest_1.it)('has a geometric modulo (self)', () => { const fraction = new fraction_1.Fraction(4, 3); (0, vitest_1.expect)(fraction.geoMod('4/3').equals(1)).toBe(true); }); // This can easily produce unrepresentable fractions. vitest_1.it.skip('has a geometric modulo (random)', () => { const fraction = new fraction_1.Fraction(Math.random()); (0, vitest_1.expect)(fraction.geoMod(Math.random()).compare(1)).toBeLessThan(0); }); (0, vitest_1.it)('has a geometric gcd (integers)', () => { const fraction = new fraction_1.Fraction(8); (0, vitest_1.expect)(fraction.gcr(4).equals(2)).toBe(true); }); (0, vitest_1.it)('has a geometric gcd (unrelated integers)', () => { const fraction = new fraction_1.Fraction(9); (0, vitest_1.expect)(fraction.gcr(4)).toBeNull(); }); (0, vitest_1.it)('has a geometric gcd (fractions)', () => { const fraction = new fraction_1.Fraction(1024, 243); (0, vitest_1.expect)(fraction.gcr('27/64').equals('4/3')).toBe(true); }); // Apparently this can "succeed" even though it should be exceedingly unlikely... vitest_1.it.skip('has a geometric gcd (random)', () => { const fraction = new fraction_1.Fraction(Math.random()); (0, vitest_1.expect)(fraction.gcr(Math.random())).toBeNull(); }); (0, vitest_1.it)('treats unity as the identity in geometric gcd (left)', () => { const fraction = new fraction_1.Fraction(12); (0, vitest_1.expect)(fraction.gcr(1).equals(12)).toBe(true); }); (0, vitest_1.it)('treats unity as the identity in geometric gcd (right)', () => { const fraction = new fraction_1.Fraction(1); (0, vitest_1.expect)(fraction.gcr(12).equals(12)).toBe(true); }); (0, vitest_1.it)('treats unity as the identity in geometric gcd (self)', () => { const fraction = new fraction_1.Fraction(1); (0, vitest_1.expect)(fraction.gcr(1).equals(1)).toBe(true); }); (0, vitest_1.it)('has logdivision (integers)', () => { const fraction = new fraction_1.Fraction(9); (0, vitest_1.expect)(fraction.log(3).equals(2)).toBe(true); }); (0, vitest_1.it)('has logdivision (negatives)', () => { const fraction = new fraction_1.Fraction(-8); (0, vitest_1.expect)(fraction.log(-2).equals(3)).toBe(true); }); (0, vitest_1.it)('has logdivision (positive/negative)', () => { const fraction = new fraction_1.Fraction(4); (0, vitest_1.expect)(fraction.log(-2).equals(2)).toBe(true); }); (0, vitest_1.it)('has logdivision (incompatible negatives)', () => { const fraction = new fraction_1.Fraction(-4); (0, vitest_1.expect)(fraction.log(-2)).toBeNull(); }); (0, vitest_1.it)('has logdivision (negative/positive)', () => { const fraction = new fraction_1.Fraction(-4); (0, vitest_1.expect)(fraction.log(2)).toBeNull(); }); (0, vitest_1.it)('has logdivision (negative result)', () => { const fraction = new fraction_1.Fraction(1, 16); (0, vitest_1.expect)(fraction.log(2).equals(-4)).toBe(true); }); (0, vitest_1.it)('has logdivision (unrelated integers)', () => { const fraction = new fraction_1.Fraction(15); (0, vitest_1.expect)(fraction.log(2)).toBeNull(); }); (0, vitest_1.it)('has logdivision (fractions)', () => { const fraction = new fraction_1.Fraction(64, 27); (0, vitest_1.expect)(fraction.log('16/9').equals('3/2')).toBe(true); }); // Apparently this can "succeed" even though it should be exceedingly unlikely... vitest_1.it.skip('has logdivision (random)', () => { const fraction = new fraction_1.Fraction(Math.random()); (0, vitest_1.expect)(fraction.log(Math.random())).toBeNull(); }); (0, vitest_1.it)('has geometric lcm (integers)', () => { const fraction = new fraction_1.Fraction(27); (0, vitest_1.expect)(fraction.lcr(81).equals(531441)).toBe(true); }); (0, vitest_1.it)('has a geometric lcm (fractions)', () => { const fraction = new fraction_1.Fraction(9, 16); // The result is subunitary for a subunitary argument by convention. (0, vitest_1.expect)(fraction.lcr('64/27').equals('729/4096')).toBe(true); }); (0, vitest_1.it)('has geometric lcm that works with unity (left)', () => { const fraction = new fraction_1.Fraction(1, 2); (0, vitest_1.expect)(fraction.lcr(1).equals(1)).toBe(true); }); (0, vitest_1.it)('has geometric lcm that works with unity (right)', () => { const fraction = new fraction_1.Fraction(1); (0, vitest_1.expect)(fraction.lcr(2).equals(1)).toBe(true); }); (0, vitest_1.it)('has geometric lcm that works with unity (both)', () => { const fraction = new fraction_1.Fraction(1); (0, vitest_1.expect)(fraction.lcr(1).equals(1)).toBe(true); }); (0, vitest_1.it)('satisfies the gcr/lcr identity for small integers when it exists', () => { for (let i = 1; i <= 10; ++i) { for (let j = 1; j <= 10; ++j) { const gcr = new fraction_1.Fraction(i).gcr(j); if (gcr === null) { continue; } const lcr = new fraction_1.Fraction(i).lcr(j); (0, vitest_1.expect)(lcr.log(i).equals(new fraction_1.Fraction(j).log(gcr))).toBe(true); } } }); (0, vitest_1.it)('satisfies the gcr/lcr identity between a small integer and a particular when it exists', () => { for (let i = 1; i <= 10; ++i) { const particular = new fraction_1.Fraction(i).inverse(); // Starting from 2 to avoid logdivision by unity. for (let j = 2; j <= 10; ++j) { const gcr = particular.gcr(j); if (gcr === null) { continue; } const lcr = particular.lcr(j); (0, vitest_1.expect)(lcr.log(particular).equals(new fraction_1.Fraction(j).log(gcr))).toBe(true); (0, vitest_1.expect)(new fraction_1.Fraction(j).gcr(particular).equals(gcr)).toBe(true); (0, vitest_1.expect)(new fraction_1.Fraction(j).lcr(particular).equals(lcr)).toBe(true); (0, vitest_1.expect)(lcr.log(j).equals(particular.log(gcr))).toBe(true); } } }); (0, vitest_1.it)('has geometric rounding (integers)', () => { const fraction = new fraction_1.Fraction(17); (0, vitest_1.expect)(fraction.geoRoundTo(2).equals(16)).toBe(true); }); (0, vitest_1.it)('has geometric rounding (positive/negative)', () => { const fraction = new fraction_1.Fraction(7); (0, vitest_1.expect)(fraction.geoRoundTo(-2).equals(4)).toBe(true); }); (0, vitest_1.it)('has geometric rounding (incompatible negative/positive)', () => { const fraction = new fraction_1.Fraction(-7); (0, vitest_1.expect)(fraction.geoRoundTo(2)).toBeNull(); }); (0, vitest_1.it)('has geometric rounding (negative)', () => { const fraction = new fraction_1.Fraction(-7); (0, vitest_1.expect)(fraction.geoRoundTo(-2).equals(-8)).toBe(true); }); (0, vitest_1.it)('has geometric rounding (fractions)', () => { const fraction = new fraction_1.Fraction(3, 2); (0, vitest_1.expect)(fraction.geoRoundTo('10/9').equals('10000/6561')).toBe(true); }); (0, vitest_1.it)('has harmonic addition', () => { const fraction = new fraction_1.Fraction('7/5'); (0, vitest_1.expect)(fraction.lensAdd('13/11').toFraction()).toBe('91/142'); }); (0, vitest_1.it)('has harmonic addition of zero (left)', () => { const fraction = new fraction_1.Fraction(0); (0, vitest_1.expect)(fraction.lensAdd('3/2').toFraction()).toBe('0'); }); (0, vitest_1.it)('has harmonic addition of zero (right)', () => { const fraction = new fraction_1.Fraction('3/2'); (0, vitest_1.expect)(fraction.lensAdd(0).toFraction()).toBe('0'); }); (0, vitest_1.it)('has harmonic addition of zero (both)', () => { const fraction = new fraction_1.Fraction(0); (0, vitest_1.expect)(fraction.lensAdd(0).toFraction()).toBe('0'); }); (0, vitest_1.it)('has harmonic subtraction', () => { const fraction = new fraction_1.Fraction('7/5'); (0, vitest_1.expect)(fraction.lensSub('13/11').toFraction()).toBe('-91/12'); }); (0, vitest_1.it)('has harmonic subtraction of zero (left)', () => { const fraction = new fraction_1.Fraction(0); (0, vitest_1.expect)(fraction.lensSub('3/2').toFraction()).toBe('0'); }); (0, vitest_1.it)('has harmonic subtraction of zero (right)', () => { const fraction = new fraction_1.Fraction('3/2'); (0, vitest_1.expect)(fraction.lensSub(0).toFraction()).toBe('0'); }); (0, vitest_1.it)('has harmonic subtraction of zero (both)', () => { const fraction = new fraction_1.Fraction(0); (0, vitest_1.expect)(fraction.lensSub(0).toFraction()).toBe('0'); }); (0, vitest_1.it)('cancels harmonic addition with harmonic subtraction', () => { const a = new fraction_1.Fraction(Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000) + 1); const b = new fraction_1.Fraction(Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000) + 1); const lensSum = a.lensAdd(b); (0, vitest_1.expect)(lensSum.lensSub(b).equals(a)).toBe(true); (0, vitest_1.expect)(lensSum.lensSub(a).equals(b)).toBe(true); }); vitest_1.it.fails('blows up on repeated division', () => { let foo = new fraction_1.Fraction('3/2'); const bar = new fraction_1.Fraction('103/101'); for (let i = 0; i < 10; ++i) { foo = foo.div(bar); } }); vitest_1.it.fails('blows up on repeated multiplication', () => { let foo = new fraction_1.Fraction('103/101'); for (let i = 0; i < 4; ++i) { foo = foo.mul(foo); } }); (0, vitest_1.it)('multiplies large cancelling factors', () => { const one = new fraction_1.Fraction('1234567890/987654321').mul('987654321/1234567890'); (0, vitest_1.expect)(one.equals(1)).toBe(true); }); (0, vitest_1.it)('adds terms with large denominators', () => { const a = new fraction_1.Fraction('123456789/94906267'); const b = new fraction_1.Fraction('987654321/94906267'); (0, vitest_1.expect)(a.add(b).equals('1111111110/94906267')).toBe(true); }); (0, vitest_1.it)('subtracts terms with large denominators', () => { const a = new fraction_1.Fraction('987654321/94906267'); const b = new fraction_1.Fraction('123456789/94906267'); (0, vitest_1.expect)(a.sub(b).equals('864197532/94906267')); }); (0, vitest_1.it)('lens-adds terms with large numerators', () => { const a = new fraction_1.Fraction('94906267/123456789'); const b = new fraction_1.Fraction('94906267/987654321'); (0, vitest_1.expect)(a.lensAdd(b).equals('94906267/1111111110')).toBe(true); }); (0, vitest_1.it)('lens-subtracts terms with large numerators', () => { const a = new fraction_1.Fraction('94906267/123456789'); const b = new fraction_1.Fraction('94906267/987654321'); (0, vitest_1.expect)(a.lensSub(b).equals('-94906267/864197532')).toBe(true); }); (0, vitest_1.it)('mods terms with large denominators', () => { const a = new fraction_1.Fraction('123456789/94906267'); const b = new fraction_1.Fraction('987654321/94906267'); (0, vitest_1.expect)(b.mod(a).equals('9/94906267')).toBe(true); }); (0, vitest_1.it)('mmods terms with large denominators', () => { const a = new fraction_1.Fraction('123456789/94906267'); const b = new fraction_1.Fraction('987654321/94906267'); (0, vitest_1.expect)(b.mmod(a).equals('9/94906267')).toBe(true); }); (0, vitest_1.it)('checks divisibility of complex fractions', () => { const a = new fraction_1.Fraction('123456789/94906267'); (0, vitest_1.expect)(a.mul(21).divisible(a)).toBe(true); }); (0, vitest_1.it)('computes gcd of factors with large denominators', () => { const a = new fraction_1.Fraction('123456789/94906267'); const b = new fraction_1.Fraction('987654321/94906267'); (0, vitest_1.expect)(a.gcd(b).equals('9/94906267')).toBe(true); }); (0, vitest_1.it)('computes lcm of factors with with large numerators', () => { const a = new fraction_1.Fraction('94906267/123456789'); const b = new fraction_1.Fraction('94906267/987654321'); (0, vitest_1.expect)(a.lcm(b).equals('94906267/9')).toBe(true); }); (0, vitest_1.it)('satisfies the multiplicative identity between gcd and lcm for small integers', () => { for (let i = -10; i <= 10; ++i) { for (let j = -10; j <= 10; ++j) { const f = new fraction_1.Fraction(i); (0, vitest_1.expect)(f .gcd(j) .mul(f.lcm(j)) .equals(i * j), `failed with ${i}, ${j}`).toBe(true); } } }); (0, vitest_1.it)('normalizes zero (integer)', () => { const fraction = new fraction_1.Fraction(0); (0, vitest_1.expect)(fraction.s).toBe(0); (0, vitest_1.expect)(fraction.n).toBe(0); (0, vitest_1.expect)(fraction.d).toBe(1); }); (0, vitest_1.it)('normalizes zero (numerator)', () => { const fraction = new fraction_1.Fraction({ n: -0, d: 1 }); (0, vitest_1.expect)(fraction.s).toBe(0); (0, vitest_1.expect)(fraction.n).toBe(0); (0, vitest_1.expect)(fraction.d).toBe(1); }); (0, vitest_1.it)('normalizes zero (denominator)', () => { const fraction = new fraction_1.Fraction({ n: 0, d: -1 }); (0, vitest_1.expect)(fraction.s).toBe(0); (0, vitest_1.expect)(fraction.n).toBe(0); (0, vitest_1.expect)(fraction.d).toBe(1); }); (0, vitest_1.it)('normalizes zero (infinite denominator)', () => { const fraction = new fraction_1.Fraction({ n: 123, d: Infinity }); (0, vitest_1.expect)(fraction.s).toBe(0); (0, vitest_1.expect)(fraction.n).toBe(0); (0, vitest_1.expect)(fraction.d).toBe(1); }); (0, vitest_1.it)('normalizes zero (infinite second argument)', () => { const fraction = new fraction_1.Fraction(-123, Infinity); (0, vitest_1.expect)(fraction.s).toBe(0); (0, vitest_1.expect)(fraction.n).toBe(0); (0, vitest_1.expect)(fraction.d).toBe(1); }); (0, vitest_1.it)('throws an informative error for (Infinity, 1)', () => { (0, vitest_1.expect)(() => new fraction_1.Fraction(Infinity, 1)).throws('Cannot represent Infinity as a fraction'); }); (0, vitest_1.it)('throws an informative error for (-Infinity, 1)', () => { (0, vitest_1.expect)(() => new fraction_1.Fraction(-Infinity, 1)).throws('Cannot represent Infinity as a fraction'); }); (0, vitest_1.it)('throws an informative error for Infinity', () => { (0, vitest_1.expect)(() => new fraction_1.Fraction(Infinity)).throws('Cannot represent Infinity as a fraction'); }); (0, vitest_1.it)('throws an informative error for {n: Infinity, d:1}', () => { (0, vitest_1.expect)(() => new fraction_1.Fraction({ n: Infinity, d: 1 })).throws('Cannot represent Infinity as a fraction'); }); (0, vitest_1.it)('throws for NaN (literal)', () => { (0, vitest_1.expect)(() => new fraction_1.Fraction(NaN)).throws('Cannot represent NaN as a fraction'); }); (0, vitest_1.it)('throws for NaN (implicit)', () => { (0, vitest_1.expect)(() => new fraction_1.Fraction(Infinity, Infinity)).throws('Cannot represent NaN as a fraction'); }); (0, vitest_1.it)('calculates the square root of 9/4', () => { const fraction = new fraction_1.Fraction(9, 4).sqrt(); (0, vitest_1.expect)(fraction.equals('3/2')).toBe(true); }); (0, vitest_1.it)('gives up on root 3', () => { const nil = new fraction_1.Fraction(3).sqrt(); (0, vitest_1.expect)(nil).toBeNull(); }); (0, vitest_1.it)('gives up on root -1', () => { const nil = new fraction_1.Fraction(-1).sqrt(); (0, vitest_1.expect)(nil).toBeNull(); }); // Passes, but takes about 409094ms vitest_1.it.skip('works on every square within the supported limit', () => { let n = 0; while (n * n < Number.MAX_SAFE_INTEGER) { (0, vitest_1.expect)(new fraction_1.Fraction(n * n).sqrt().equals(n)).toBe(true); ++n; } }); }); (0, vitest_1.describe)('JSON serialization', () => { (0, vitest_1.it)('can serialize an array of fractions along with other data', () => { const serialized = JSON.stringify([ new fraction_1.Fraction(42), 2, new fraction_1.Fraction(-5, 3), new fraction_1.Fraction('1.234'), 'hello', new fraction_1.Fraction({ s: 0, n: 0, d: 1 }), null, ]); (0, vitest_1.expect)(serialized).toBe('[{"n":42,"d":1},2,{"n":-5,"d":3},{"n":617,"d":500},"hello",{"n":0,"d":1},null]'); }); (0, vitest_1.it)('can revive an array of fractions along with other data', () => { const serialized = '[{"n":42,"d":1},2,{"n":-5,"d":3},{"n":617,"d":500},"hello",{"n":0,"d":1},null]'; const data = JSON.parse(serialized, fraction_1.Fraction.reviver); (0, vitest_1.expect)(data).toHaveLength(7); (0, vitest_1.expect)(data[0]).toBeInstanceOf(fraction_1.Fraction); (0, vitest_1.expect)(data[0]).toEqual({ s: 1, n: 42, d: 1 }); (0, vitest_1.expect)(data[1]).toBe(2); (0, vitest_1.expect)(data[2]).toBeInstanceOf(fraction_1.Fraction); (0, vitest_1.expect)(data[2]).toEqual({ s: -1, n: 5, d: 3 }); (0, vitest_1.expect)(data[3]).toBeInstanceOf(fraction_1.Fraction); (0, vitest_1.expect)(data[3].equals('1.234')).toBe(true); (0, vitest_1.expect)(data[4]).toBe('hello'); (0, vitest_1.expect)(data[5]).toBeInstanceOf(fraction_1.Fraction); (0, vitest_1.expect)(data[5]).toEqual({ s: 0, n: 0, d: 1 }); (0, vitest_1.expect)(data[6]).toBeNull(); }); (0, vitest_1.it)('either parses or rejects increasingly accurate decimals', () => { for (let i = 1; i < 20; ++i) { try { const f = new fraction_1.Fraction('0.' + '9'.repeat(i)); (0, vitest_1.expect)(f.toFraction()).toBe('9'.repeat(i) + '/' + '1' + '0'.repeat(i)); } catch (e) { const message = e instanceof Error ? e.message : String(e); (0, vitest_1.expect)(message).toBe('Decimal string too complex'); } } }); }); //# sourceMappingURL=fraction.spec.js.map