diff --git a/src/__tests__/monzo.spec.ts b/src/__tests__/monzo.spec.ts index dbfc3ae..ed7436e 100644 --- a/src/__tests__/monzo.spec.ts +++ b/src/__tests__/monzo.spec.ts @@ -77,11 +77,16 @@ describe('Fraction to monzo converter', () => { }); it('throws for zero', () => { - expect(() => toMonzoAndResidual(0, 1)).toThrow(); + const [monzo, residual] = toMonzoAndResidual(0, 1); + expect(residual.equals(0)).toBeTruthy(); + expect(monzo).toHaveLength(1); + expect(new Fraction(2).pow(monzo[0]).mul(residual).equals(0)).toBeTruthy(); }); it('throws for zero (no vector part)', () => { - expect(() => toMonzoAndResidual(0, 0)).toThrow(); + const [monzo, residual] = toMonzoAndResidual(0, 0); + expect(residual.equals(0)).toBeTruthy(); + expect(monzo).toHaveLength(0); }); }); @@ -106,12 +111,16 @@ describe('Prime limit calculator', () => { expect(primeLimit(45)).toBe(5); }); + it('knows that the prime limit of 21 has ordinal #4', () => { + expect(primeLimit(21, true)).toBe(4); + }); + it('knows that the prime limit of 11859211/11859210 is 19', () => { expect(primeLimit('11859211/11859210')).toBe(19); }); it('returns infinity when going beyond the given limit', () => { - expect(primeLimit(123456789, 97)).toBe(Infinity); + expect(primeLimit(123456789, false, 97)).toBe(Infinity); }); it('stays within the given limit', () => { @@ -120,6 +129,7 @@ describe('Prime limit calculator', () => { Math.ceil(Math.random() * 10000), Math.ceil(Math.random() * 10000) ), + false, 97 ); if (limit < Infinity) { diff --git a/src/monzo.ts b/src/monzo.ts index 77c464b..7488667 100644 --- a/src/monzo.ts +++ b/src/monzo.ts @@ -204,7 +204,7 @@ export function toMonzoAndResidual( const denominator = n.d; if (!n.n) { - throw new Error('Cannot convert zero to monzo'); + return [Array(numberOfComponents).fill(0), new Fraction(0)]; } let nProbe = 1; @@ -261,19 +261,27 @@ export function monzoToFraction(monzo: Iterable) { /** * Calculate the prime limit of an integer or a fraction. * @param n Integer or fraction to calculate prime limit for. + * @param asOrdinal Return the limit as an ordinal instead of a prime. (1 is #0, 2 is #1, 3 is #2, 5 is #3, etc.) * @param maxLimit Maximum prime limit to consider. * @returns The largest prime in the factorization of the input. `Infinity` if above the maximum limit. `NaN` if not applicable. */ -export function primeLimit(n: FractionValue, maxLimit = 7919): number { +export function primeLimit( + n: FractionValue, + asOrdinal = false, + maxLimit = 7919 +): number { if (typeof n !== 'number') { n = new Fraction(n); - return Math.max(primeLimit(n.n, maxLimit), primeLimit(n.d, maxLimit)); + return Math.max( + primeLimit(n.n, asOrdinal, maxLimit), + primeLimit(n.d, asOrdinal, maxLimit) + ); } if (n < 1 || Math.round(n) !== n) { return NaN; } if (n === 1) { - return 1; + return asOrdinal ? 0 : 1; } // Accumulate increasingly complex factors into the probe @@ -285,7 +293,7 @@ export function primeLimit(n: FractionValue, maxLimit = 7919): number { // Bit-magic for small 2-limit probe = (n ^ (n - 1)) & n; if (n === probe) { - return 2; + return asOrdinal ? 1 : 2; } limitIndex = 1; } @@ -300,7 +308,7 @@ export function primeLimit(n: FractionValue, maxLimit = 7919): number { return Infinity; } } else if (n === probe) { - return PRIMES[limitIndex]; + return asOrdinal ? limitIndex + 1 : PRIMES[limitIndex]; } } }