168 lines
3.8 KiB
JavaScript
168 lines
3.8 KiB
JavaScript
|
|
// Check if a number is prime (works with Number type)
|
||
|
|
function isPrime(n) {
|
||
|
|
if (n < 2) return false;
|
||
|
|
if (n % 2 === 0) return n === 2;
|
||
|
|
if (n % 3 === 0) return n === 3;
|
||
|
|
for (let p = 5; p * p <= n; p += 4) {
|
||
|
|
if (n % p === 0) return false;
|
||
|
|
p += 2;
|
||
|
|
if (n % p === 0) return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Rotate the digits of a number: 197 -> 719 -> 971
|
||
|
|
function cycle(n) {
|
||
|
|
const s = n.toString();
|
||
|
|
if (s.length === 1) return n;
|
||
|
|
return parseInt(s.slice(1) + s[0], 10);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if a number is a circular prime
|
||
|
|
function isCircularPrime(p) {
|
||
|
|
if (!isPrime(p)) return false;
|
||
|
|
|
||
|
|
// Quick check: multi-digit circular primes cannot contain 0,2,4,5,6,8
|
||
|
|
const s = p.toString();
|
||
|
|
if (s.length > 1 && /[024568]/.test(s)) return false;
|
||
|
|
|
||
|
|
let p2 = cycle(p);
|
||
|
|
while (p2 !== p) {
|
||
|
|
if (p2 < p || !isPrime(p2)) return false;
|
||
|
|
p2 = cycle(p2);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Modular exponentiation: (base^exp) % mod
|
||
|
|
function powerMod(base, exp, mod) {
|
||
|
|
let result = 1n;
|
||
|
|
base = base % mod;
|
||
|
|
while (exp > 0n) {
|
||
|
|
if (exp % 2n === 1n) result = (result * base) % mod;
|
||
|
|
exp >>= 1n;
|
||
|
|
base = (base * base) % mod;
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate random BigInt in range [min, max] (inclusive)
|
||
|
|
function generateRandomBigInt(min, max) {
|
||
|
|
if (max < min) throw new Error("Invalid range");
|
||
|
|
|
||
|
|
const range = max - min + 1n;
|
||
|
|
// If range is small enough, use Math.random
|
||
|
|
if (range <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
||
|
|
return min + BigInt(Math.floor(Math.random() * Number(range)));
|
||
|
|
}
|
||
|
|
|
||
|
|
// For large ranges, generate a random number with appropriate digit length
|
||
|
|
const maxStr = max.toString();
|
||
|
|
const len = maxStr.length;
|
||
|
|
|
||
|
|
let randomStr;
|
||
|
|
do {
|
||
|
|
// Generate a number with one fewer digit than max
|
||
|
|
randomStr = '';
|
||
|
|
for (let i = 0; i < len - 1; i++) {
|
||
|
|
randomStr += Math.floor(Math.random() * 10).toString();
|
||
|
|
}
|
||
|
|
// Remove leading zeros and ensure it's not empty
|
||
|
|
randomStr = randomStr.replace(/^0+/, '');
|
||
|
|
if (randomStr === '') randomStr = '0';
|
||
|
|
} while (BigInt(randomStr) > max);
|
||
|
|
|
||
|
|
const result = BigInt(randomStr);
|
||
|
|
return result >= min ? result : min;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Miller-Rabin primality test for BigInt
|
||
|
|
function isProbablePrime(n, k = 15) {
|
||
|
|
if (n === 2n || n === 3n) return true;
|
||
|
|
if (n < 2n || n % 2n === 0n) return false;
|
||
|
|
|
||
|
|
// Write n-1 as d*2^s
|
||
|
|
let d = n - 1n;
|
||
|
|
let s = 0;
|
||
|
|
while (d % 2n === 0n) {
|
||
|
|
d /= 2n;
|
||
|
|
s++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Witness loop
|
||
|
|
for (let i = 0; i < k; i++) {
|
||
|
|
// Generate random BigInt a in range [2, n-2]
|
||
|
|
const a = generateRandomBigInt(2n, n - 2n);
|
||
|
|
|
||
|
|
let x = powerMod(a, d, n);
|
||
|
|
if (x === 1n || x === n - 1n) continue;
|
||
|
|
|
||
|
|
let composite = true;
|
||
|
|
for (let r = 0; r < s - 1; r++) {
|
||
|
|
x = (x * x) % n;
|
||
|
|
if (x === n - 1n) {
|
||
|
|
composite = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (composite) return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a repunit: digits=3 -> 111n
|
||
|
|
function repunit(digits) {
|
||
|
|
return (10n ** BigInt(digits) - 1n) / 9n;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test repunit primality
|
||
|
|
function testRepunit(digits) {
|
||
|
|
const r = repunit(digits);
|
||
|
|
if (isProbablePrime(r, 15)) {
|
||
|
|
console.log(`R(${digits}) is probably prime.`);
|
||
|
|
} else {
|
||
|
|
console.log(`R(${digits}) is not prime.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Main execution
|
||
|
|
console.log("First 19 circular primes:");
|
||
|
|
let p = 2;
|
||
|
|
let count = 0;
|
||
|
|
while (count < 19) {
|
||
|
|
if (isCircularPrime(p)) {
|
||
|
|
if (count > 0) process.stdout.write(", ");
|
||
|
|
process.stdout.write(p.toString());
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
p++;
|
||
|
|
}
|
||
|
|
console.log();
|
||
|
|
|
||
|
|
console.log("Next 4 circular primes:");
|
||
|
|
let digits = 1;
|
||
|
|
let repunitVal = 1n;
|
||
|
|
while (repunitVal < BigInt(p)) {
|
||
|
|
digits++;
|
||
|
|
repunitVal = repunit(digits);
|
||
|
|
}
|
||
|
|
|
||
|
|
count = 0;
|
||
|
|
while (count < 4) {
|
||
|
|
if (isProbablePrime(repunitVal, 15)) {
|
||
|
|
if (count > 0) process.stdout.write(", ");
|
||
|
|
process.stdout.write(`R(${digits})`);
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
digits++;
|
||
|
|
repunitVal = repunitVal * 10n + 1n;
|
||
|
|
}
|
||
|
|
console.log();
|
||
|
|
|
||
|
|
testRepunit(5003);
|
||
|
|
testRepunit(9887);
|
||
|
|
testRepunit(15073);
|
||
|
|
testRepunit(25031);
|