diff --git a/src/__tests__/primes.spec.ts b/src/__tests__/primes.spec.ts index f0bd3a9..e5d0390 100644 --- a/src/__tests__/primes.spec.ts +++ b/src/__tests__/primes.spec.ts @@ -1,22 +1,80 @@ import {describe, it, expect} from 'vitest'; -import {PRIMES} from '../primes'; +import {PRIMES, isPrime, primes} from '../primes'; describe('Array of prime numbers', () => { it('has no gaps', () => { expect(PRIMES[0]).toBe(2); let index = 1; for (let n = 3; n <= 7919; n += 2) { - let isPrime = true; + let isPrime_ = true; for (let i = 3; i * i <= n; ++i) { if (n % i === 0) { - isPrime = false; + isPrime_ = false; break; } } - if (isPrime) { + if (isPrime_) { expect(PRIMES[index]).toBe(n); index++; } } }); }); + +const LARGER_PRIMES = [ + 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, 8039, 8053, 8059, + 8069, 8081, 8087, 8089, 8093, +]; + +describe('Primeness detector', () => { + it('works for small primes', () => { + for (let n = -1.5; n < 7920.5; n += 0.5) { + if (isPrime(n)) { + expect(PRIMES).includes(n); + } else { + expect(PRIMES).not.includes(n); + } + } + }); + it.each(LARGER_PRIMES)('works for %s', (prime: number) => { + expect(isPrime(prime)).toBe(true); + }); + it('works for larger composites', () => { + for (let n = 7919.25; n < 8095; n += 0.25) { + if (LARGER_PRIMES.includes(n)) { + continue; + } + expect(isPrime(n)).toBe(false); + } + }); +}); + +describe('Lists of primes', () => { + it('works from implicit 2 to 7', () => { + expect(primes(7)).toEqual([2, 3, 5, 7]); + }); + + it('works from implicit 2 to below 16', () => { + expect(primes(16)).toEqual([2, 3, 5, 7, 11, 13]); + }); + + it('produces an empty array with negative bounds', () => { + expect(primes(-100, -50)).toHaveLength(0); + }); + + it('produces an empty array when end < start', () => { + expect(primes(10, 1)).toHaveLength(0); + }); + + it('supports start and end parameters', () => { + expect(primes(12, 31)).toEqual([13, 17, 19, 23, 29, 31]); + }); + + it('works with medium-sized values', () => { + expect(primes(7907, 7933)).toEqual([7907, 7919, 7927, 7933]); + }); + + it('works with larger values', () => { + expect(primes(7920, 8094)).toEqual(LARGER_PRIMES); + }); +}); diff --git a/src/primes.ts b/src/primes.ts index 046942e..481be00 100644 --- a/src/primes.ts +++ b/src/primes.ts @@ -101,3 +101,79 @@ PRIME_CENTS[0] = 1200; // Ensure that octaves are exact. * BigInt representation of the primes. */ export const BIG_INT_PRIMES = PRIMES.map(BigInt); + +/** + * Check a number for primality. + * @param n Number to check. + * @returns True if the number is prime, false otherwise. + */ +export function isPrime(n: number) { + if (!Number.isInteger(n) || n < 2) { + return false; + } + if (n < 7927) { + return PRIMES.includes(n); + } + if (n > 62837328) { + throw new Error('Prime check only implemented up to 62837328'); + } + for (const prime of PRIMES) { + if (n % prime === 0) { + return false; + } + } + return true; +} + +/** + * Generate an array of prime numbers. + * @param start Smallest prime number to include (or the next smallest prime). + * @param end Largest prime number to include (or the previous largest prime). + * @returns An array of primes p in ascending order such that start <= p <= end. + */ +export function primes(start: number, end?: number) { + if (end === undefined) { + end = start; + start = 2; + } + if (start > end || end < 2) { + return []; + } + if (start <= 2) { + if (end < 3) { + return [2]; + } + start = 2; + } else { + start = Math.ceil((start + 1) * 0.5) * 2 - 1; + while (!isPrime(start)) { + start += 2; + } + } + end = Math.floor((end + 1) * 0.5) * 2 - 1; + while (!isPrime(end)) { + end -= 2; + } + + const startIndex = PRIMES.indexOf(start); + const endIndex = PRIMES.indexOf(end); + + let result: number[]; + if (endIndex < 0) { + if (startIndex >= 0) { + result = PRIMES.slice(startIndex); + start = 7927; + } else { + result = []; + } + } else { + return PRIMES.slice(startIndex, endIndex + 1); + } + + for (let n = start; n <= end; n += 2) { + if (isPrime(n)) { + result.push(n); + } + } + return result; +}