xen-dev-utils
Version:
Utility functions used by the Scale Workshop ecosystem
404 lines • 18.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const fraction_1 = require("../fraction");
const monzo_1 = require("../monzo");
const primes_1 = require("../primes");
const FUZZ = 'FUZZ' in process.env;
function toMonzoAndResidual11(n) {
const result = [0, 0, 0, 0, 0];
if (!n) {
return [result, n];
}
while (n % 2 === 0) {
n /= 2;
result[0]++;
}
while (n % 3 === 0) {
n /= 3;
result[1]++;
}
while (n % 5 === 0) {
n /= 5;
result[2]++;
}
while (n % 7 === 0) {
n /= 7;
result[3]++;
}
while (n % 11 === 0) {
n /= 11;
result[4]++;
}
return [result, n];
}
(0, vitest_1.describe)('Monzo converter', () => {
(0, vitest_1.it)('can break down an integer to its prime components', () => {
const monzo = (0, monzo_1.toMonzo)(360);
(0, vitest_1.expect)(monzo[0]).toBe(3);
(0, vitest_1.expect)(monzo[1]).toBe(2);
(0, vitest_1.expect)(monzo[2]).toBe(1);
(0, vitest_1.expect)(2 ** monzo[0] * 3 ** monzo[1] * 5 ** monzo[2]).toBe(360);
});
(0, vitest_1.it)('can break down a fraction to its prime components', () => {
const monzo = (0, monzo_1.toMonzo)('1029/1024');
(0, vitest_1.expect)(monzo[0]).toBe(-10);
(0, vitest_1.expect)(monzo[1]).toBe(1);
(0, vitest_1.expect)(monzo[2]).toBe(0);
(0, vitest_1.expect)(monzo[3]).toBe(3);
(0, vitest_1.expect)(new fraction_1.Fraction(2)
.pow(monzo[0])
.mul(3 ** monzo[1] * 7 ** monzo[3])
.equals('1029/1024')).toBeTruthy();
});
(0, vitest_1.it)('can break down a fraction to its prime components (5-limit)', () => {
const porcupineComma = (0, monzo_1.toMonzo)('250/243');
(0, vitest_1.expect)(porcupineComma.length).toBe(3);
(0, vitest_1.expect)(porcupineComma[0]).toBe(1);
(0, vitest_1.expect)(porcupineComma[1]).toBe(-5);
(0, vitest_1.expect)(porcupineComma[2]).toBe(3);
});
(0, vitest_1.it)('throws for zero', () => {
(0, vitest_1.expect)(() => (0, monzo_1.toMonzo)(0)).toThrow();
});
(0, vitest_1.it)('can break down a big integer to its prime components', () => {
const monzo = (0, monzo_1.toMonzo)(BigInt('360000000000000000000000'));
(0, vitest_1.expect)(monzo[0]).toBe(24);
(0, vitest_1.expect)(monzo[1]).toBe(2);
(0, vitest_1.expect)(monzo[2]).toBe(22);
(0, vitest_1.expect)(BigInt(2) ** BigInt(monzo[0]) *
BigInt(3) ** BigInt(monzo[1]) *
BigInt(5) ** BigInt(monzo[2])).toBe(BigInt('360000000000000000000000'));
});
(0, vitest_1.it)('works just below the int32 boundary', () => {
const monzo = (0, monzo_1.toMonzo)(2 ** 30);
(0, vitest_1.expect)(monzo).toEqual([30]);
});
(0, vitest_1.it)('works at the int32 boundary', () => {
const monzo = (0, monzo_1.toMonzo)(2 ** 31);
(0, vitest_1.expect)(monzo).toEqual([31]);
});
(0, vitest_1.it)('works just above the int32 boundary', () => {
const monzo = (0, monzo_1.toMonzo)(2 ** 32);
(0, vitest_1.expect)(monzo).toEqual([32]);
});
(0, vitest_1.it)('works just below the IEEE limit', () => {
const monzo = (0, monzo_1.toMonzo)(2n ** 1023n);
(0, vitest_1.expect)(monzo).toEqual([1023]);
});
(0, vitest_1.it)('works at the IEEE limit', () => {
const monzo = (0, monzo_1.toMonzo)(2n ** 1024n);
(0, vitest_1.expect)(monzo).toEqual([1024]);
});
(0, vitest_1.it)('works just above the IEEE limit', () => {
const monzo = (0, monzo_1.toMonzo)(2n ** 1025n);
(0, vitest_1.expect)(monzo).toEqual([1025]);
});
(0, vitest_1.it)('agrees with the reference implementation', () => {
for (let n = 1; n <= 1000; ++n) {
const monzo = (0, monzo_1.toMonzo)(n);
const bigMonzo = (0, monzo_1.toMonzo)(BigInt(n));
let [reference, refResidual] = toMonzoAndResidual11(n);
reference = reference.slice(0, monzo.length);
(0, vitest_1.expect)(monzo.slice(0, 5)).toEqual(reference);
(0, vitest_1.expect)(bigMonzo.slice(0, 5)).toEqual(reference);
if (refResidual !== 1) {
(0, vitest_1.expect)(monzo.length).toBeGreaterThan(5);
(0, vitest_1.expect)(bigMonzo.length).toBeGreaterThan(5);
}
else {
(0, vitest_1.expect)(monzo.length).toBeLessThanOrEqual(5);
(0, vitest_1.expect)(bigMonzo.length).toBeLessThanOrEqual(5);
}
}
});
(0, vitest_1.it)('refuses to factor a negative fraction', () => {
(0, vitest_1.expect)(() => (0, monzo_1.toMonzo)('-1/2')).toThrow('Cannot convert fraction -1/2 to monzo');
});
});
(0, vitest_1.describe)('Fraction to monzo converter', () => {
(0, vitest_1.it)('can break down a fraction to its prime components', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(new fraction_1.Fraction(45, 32), 3);
(0, vitest_1.expect)(residual.equals(1)).toBeTruthy();
(0, vitest_1.expect)(monzo[0]).toBe(-5);
(0, vitest_1.expect)(monzo[1]).toBe(2);
(0, vitest_1.expect)(monzo[2]).toBe(1);
(0, vitest_1.expect)(new fraction_1.Fraction(2)
.pow(monzo[0])
.mul(3 ** monzo[1])
.mul(5 ** monzo[2])
.equals(new fraction_1.Fraction(45, 32))).toBeTruthy();
});
(0, vitest_1.it)('leaves a residue if everything cannot be converted', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)('12345/678', 3);
(0, vitest_1.expect)(residual.equals('823/113')).toBeTruthy();
(0, vitest_1.expect)(monzo).toHaveLength(3);
(0, vitest_1.expect)(monzo[0]).toBe(-1);
(0, vitest_1.expect)(monzo[1]).toBe(0);
(0, vitest_1.expect)(monzo[2]).toBe(1);
(0, vitest_1.expect)(new fraction_1.Fraction(2)
.pow(monzo[0])
.mul(3 ** monzo[1])
.mul(5 ** monzo[2])
.mul(residual)
.equals('12345/678')).toBeTruthy();
});
(0, vitest_1.it)('converts a Pythagorean interval', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)('129140163/134217728', 6);
(0, vitest_1.expect)(monzo).toEqual([-27, 17, 0, 0, 0, 0]);
(0, vitest_1.expect)(residual.equals(1)).toBe(true);
});
(0, vitest_1.it)('leaves residual 0 for zero (vector part)', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(0, 1);
(0, vitest_1.expect)(residual.equals(0)).toBeTruthy();
(0, vitest_1.expect)(monzo).toHaveLength(1);
(0, vitest_1.expect)(new fraction_1.Fraction(2).pow(monzo[0]).mul(residual).equals(0)).toBeTruthy();
});
(0, vitest_1.it)('leaves residual 0 for zero (no vector part)', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(0, 0);
(0, vitest_1.expect)(residual.equals(0)).toBeTruthy();
(0, vitest_1.expect)(monzo).toHaveLength(0);
});
(0, vitest_1.it)('leaves a residue if everything cannot be converted', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(BigInt('123456789000000000000'), 3);
(0, vitest_1.expect)(residual).toBe(BigInt(13717421));
(0, vitest_1.expect)(monzo).toHaveLength(3);
(0, vitest_1.expect)(monzo[0]).toBe(12);
(0, vitest_1.expect)(monzo[1]).toBe(2);
(0, vitest_1.expect)(monzo[2]).toBe(12);
(0, vitest_1.expect)(BigInt(2) ** BigInt(monzo[0]) *
BigInt(3) ** BigInt(monzo[1]) *
BigInt(5) ** BigInt(monzo[2]) *
residual).toBe(BigInt('123456789000000000000'));
});
(0, vitest_1.it)('leaves residual 0n for big int zero', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(0n, 1);
(0, vitest_1.expect)(residual).toBe(0n);
(0, vitest_1.expect)(monzo).toHaveLength(1);
});
(0, vitest_1.it)('leaves residual 0n for big int zero (no vector part)', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(0n, 0);
(0, vitest_1.expect)(residual).toBe(0n);
(0, vitest_1.expect)(monzo).toHaveLength(0);
});
(0, vitest_1.it)('leaves negative residual for big integers', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(-10n, 2);
(0, vitest_1.expect)(residual).toBe(-5n);
(0, vitest_1.expect)(monzo).toHaveLength(2);
(0, vitest_1.expect)(monzo[0]).toBe(1);
(0, vitest_1.expect)(monzo[1]).toBe(0);
});
(0, vitest_1.it)('leaves negative residual for integers', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(-10, 2);
(0, vitest_1.expect)(residual.toFraction()).toBe('-5');
(0, vitest_1.expect)(monzo).toHaveLength(2);
(0, vitest_1.expect)(monzo[0]).toBe(1);
(0, vitest_1.expect)(monzo[1]).toBe(0);
});
(0, vitest_1.it)('works just below the int32 boundary', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(2 ** 30, 1);
(0, vitest_1.expect)(monzo).toEqual([30]);
(0, vitest_1.expect)(residual.isUnity()).toBe(true);
});
(0, vitest_1.it)('works at the int32 boundary', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(2 ** 31, 1);
(0, vitest_1.expect)(monzo).toEqual([31]);
(0, vitest_1.expect)(residual.isUnity()).toBe(true);
});
(0, vitest_1.it)('works just above the int32 boundary', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(2 ** 32, 1);
(0, vitest_1.expect)(monzo).toEqual([32]);
(0, vitest_1.expect)(residual.isUnity()).toBe(true);
});
(0, vitest_1.it)('works just below the IEEE limit', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(2n ** 1023n, 1);
(0, vitest_1.expect)(monzo).toEqual([1023]);
(0, vitest_1.expect)(residual).toBe(1n);
});
(0, vitest_1.it)('works at the IEEE limit', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(2n ** 1024n, 1);
(0, vitest_1.expect)(monzo).toEqual([1024]);
(0, vitest_1.expect)(residual).toBe(1n);
});
(0, vitest_1.it)('works just above the IEEE limit', () => {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(2n ** 1025n, 1);
(0, vitest_1.expect)(monzo).toEqual([1025]);
(0, vitest_1.expect)(residual).toBe(1n);
});
(0, vitest_1.it)('agrees with the reference implementation', () => {
for (let n = -1000; n <= 1000; ++n) {
const [monzo, residual] = (0, monzo_1.toMonzoAndResidual)(n, 5);
const [bigMonzo, bigResidual] = (0, monzo_1.toMonzoAndResidual)(BigInt(n), 5);
const [reference, refResidual] = toMonzoAndResidual11(n);
(0, vitest_1.expect)(monzo).toEqual(reference);
(0, vitest_1.expect)(bigMonzo).toEqual(reference);
(0, vitest_1.expect)(residual.equals(refResidual)).toBe(true);
(0, vitest_1.expect)(bigResidual).toBe(BigInt(refResidual));
}
});
});
(0, vitest_1.describe)('Monzo to fraction converter', () => {
(0, vitest_1.it)('multiplies the prime components', () => {
(0, vitest_1.expect)((0, monzo_1.monzoToFraction)([3, -2, -1]).equals(new fraction_1.Fraction(8, 45))).toBeTruthy();
});
});
(0, vitest_1.describe)('Monzo to BigInt converter', () => {
(0, vitest_1.it)('multiplies the prime components', () => {
(0, vitest_1.expect)((0, monzo_1.monzoToBigInt)([30, 20, 10])).toBe(BigInt('36561584400629760000000000'));
});
});
(0, vitest_1.describe)('Prime limit calculator', () => {
(0, vitest_1.it)('knows that the limit of 1 is 1', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(1)).toBe(1);
});
(0, vitest_1.it)('knows that the prime limit of 64 is 2', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(64)).toBe(2);
});
(0, vitest_1.it)('knows that the prime limit of 45 is 5', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(45)).toBe(5);
});
(0, vitest_1.it)('knows that the prime limit of 21 has ordinal #4', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(21, true)).toBe(4);
});
(0, vitest_1.it)('knows that the prime limit of 11859211/11859210 is 19', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)('11859211/11859210')).toBe(19);
});
(0, vitest_1.it)('returns infinity when going beyond the given limit', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(123456789, false, 97)).toBe(Infinity);
});
(0, vitest_1.it)('stays within the given limit', () => {
const limit = (0, monzo_1.primeLimit)(new fraction_1.Fraction(Math.ceil(Math.random() * 10000), Math.ceil(Math.random() * 10000)), false, 97);
if (limit < Infinity) {
(0, vitest_1.expect)(limit).toBeLessThanOrEqual(97);
}
else {
(0, vitest_1.expect)(limit).toBe(Infinity);
}
});
(0, vitest_1.it)('can handle large inputs', () => {
const limit = (0, monzo_1.primeLimit)(new fraction_1.Fraction(4294967296, 4006077075));
(0, vitest_1.expect)(limit).toBe(13);
});
(0, vitest_1.it)('can handle BigInt inputs', () => {
const two = (0, monzo_1.primeLimit)(BigInt('1267650600228229401496703205376'));
(0, vitest_1.expect)(two).toBe(2);
const limit = (0, monzo_1.primeLimit)(BigInt('1561327220802586898249028'));
(0, vitest_1.expect)(limit).toBe(19);
});
(0, vitest_1.it)('works just below the int32 boundary', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(2 ** 30)).toEqual(2);
});
(0, vitest_1.it)('works at the int32 boundary', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(2 ** 31)).toEqual(2);
});
(0, vitest_1.it)('works just above the int32 boundary', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(2 ** 32)).toEqual(2);
});
(0, vitest_1.it)('works just below the IEEE limit', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(2n ** 1023n)).toEqual(2);
});
(0, vitest_1.it)('works at the IEEE limit', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(2n ** 1024n)).toEqual(2);
});
(0, vitest_1.it)('works just above the IEEE limit', () => {
(0, vitest_1.expect)((0, monzo_1.primeLimit)(2n ** 1025n)).toEqual(2);
});
});
(0, vitest_1.describe)('Sparse monzos', () => {
(0, vitest_1.it)('factorizes 12', () => {
const exponentByPrime = (0, monzo_1.primeFactorize)(12);
(0, vitest_1.expect)(exponentByPrime).toHaveLength(2);
(0, vitest_1.expect)(exponentByPrime.get(2)).toBe(2);
(0, vitest_1.expect)(exponentByPrime.get(3)).toBe(1);
});
(0, vitest_1.it)('factorizes 0', () => {
const factors = (0, monzo_1.primeFactorize)(0);
(0, vitest_1.expect)(factors).toHaveLength(1);
(0, vitest_1.expect)(factors.get(0)).toBe(1);
});
(0, vitest_1.it)('factorizes -35', () => {
const factors = (0, monzo_1.primeFactorize)(-35);
(0, vitest_1.expect)(factors).toHaveLength(3);
(0, vitest_1.expect)(factors.get(-1)).toBe(1);
(0, vitest_1.expect)(factors.get(5)).toBe(1);
(0, vitest_1.expect)(factors.get(7)).toBe(1);
});
(0, vitest_1.it)('factorizes 81/80', () => {
const factors = (0, monzo_1.primeFactorize)('81/80');
(0, vitest_1.expect)(factors).toHaveLength(3);
(0, vitest_1.expect)(factors.get(2)).toBe(-4);
(0, vitest_1.expect)(factors.get(3)).toBe(4);
(0, vitest_1.expect)(factors.get(5)).toBe(-1);
});
(0, vitest_1.it)('factorizes 1073741823', () => {
const factors = (0, monzo_1.primeFactorize)(1073741823);
(0, vitest_1.expect)(factors).toHaveLength(6);
(0, vitest_1.expect)(factors.get(3)).toBe(2);
(0, vitest_1.expect)(factors.get(7)).toBe(1);
(0, vitest_1.expect)(factors.get(11)).toBe(1);
(0, vitest_1.expect)(factors.get(31)).toBe(1);
(0, vitest_1.expect)(factors.get(151)).toBe(1);
(0, vitest_1.expect)(factors.get(331)).toBe(1);
});
vitest_1.it.each([
49305423,
4104956,
8375509,
27826943,
44852222,
22932439,
46933379,
59598447,
9693451,
54191546,
61834729,
26866018,
46410510,
47335837,
43839566,
27470 * 5043,
17719 * 21909,
15812 * 27083,
21097 * 29468,
30038 * 7605,
])('works on a tough case %s found during fuzzing', n => {
const exponentByPrime = (0, monzo_1.primeFactorize)(n);
let m = 1;
for (const prime of exponentByPrime.keys()) {
(0, vitest_1.expect)((0, primes_1.isPrime)(prime)).toBe(true);
m *= prime ** exponentByPrime.get(prime);
}
(0, vitest_1.expect)(m).toBe(n);
});
vitest_1.it.runIf(FUZZ)('fuzzes for more broken cases', () => {
for (let i = 0; i < 100; ++i) {
const n = Math.floor(Math.random() * 62837328) + 1;
const exponentByPrime = (0, monzo_1.primeFactorize)(n);
let m = 1;
for (const prime of exponentByPrime.keys()) {
(0, vitest_1.expect)((0, primes_1.isPrime)(prime)).toBe(true);
m *= prime ** exponentByPrime.get(prime);
}
(0, vitest_1.expect)(m).toBe(n);
}
});
});
(0, vitest_1.describe)('Fractional monzo methods', () => {
(0, vitest_1.it)('test for equality between two monzos (equal)', () => {
const yes = (0, monzo_1.fractionalMonzosEqual)(['1/2', '7/9'], [0.5, new fraction_1.Fraction(14, 18), 0]);
(0, vitest_1.expect)(yes).toBe(true);
});
(0, vitest_1.it)('test for equality between two monzos (not equal)', () => {
const no = (0, monzo_1.fractionalMonzosEqual)(['1/2', '7/9'], [0.75, new fraction_1.Fraction(7, 9)]);
(0, vitest_1.expect)(no).toBe(false);
});
(0, vitest_1.it)('adds two fractional monzos', () => {
const result = (0, monzo_1.fractionalAdd)(['1/2', '2/3'], [new fraction_1.Fraction(1), 0.75]);
(0, vitest_1.expect)(result.map(f => f.toFraction())).toEqual(['3/2', '17/12']);
});
(0, vitest_1.it)('measures the naïve squared length of a fractional monzo', () => {
const l2 = (0, monzo_1.fractionalNorm)([0.5, '1/3', '5/7']);
(0, vitest_1.expect)(l2.toFraction()).toBe('1537/1764');
});
});
//# sourceMappingURL=monzo.spec.js.map