xen-dev-utils
Version:
Utility functions used by the Scale Workshop ecosystem
688 lines • 35.1 kB
JavaScript
"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