UNPKG

xen-dev-utils

Version:

Utility functions used by the Scale Workshop ecosystem

404 lines 18.8 kB
"use strict"; 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