The Number Field Sieve's secret weapon is smooth numbers — integers whose prime factors are all small. The sieve step scans a huge range of candidates and keeps only those that factor entirely over a factor base of small primes (up to bound B). Those smooth values become rows in a giant matrix; when enough rows are collected, linear algebra over F2 spits out a factorization.
<p class="hint">The NFS sieve step hunts for <b>B-smooth numbers</b> — integers whose largest prime factor is ≤ B. Drag the slider to change B and see how many of the first 200 positive integers qualify. More smooth numbers = more "relations" to feed the linear-algebra step.</p>
<div class="controls">
<label for="bSlider">Smoothness bound B = <strong id="bVal">20</strong></label>
<input id="bSlider" type="range" min="2" max="97" value="20" step="1">
</div>
<div class="stats" id="stats"></div>
<div id="grid" class="grid"></div>
<div class="btns">
<button id="btnFactor" type="button">Factor a highlighted number</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div class="output" id="output"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .6rem; }
.controls label { font-size: .92rem; font-weight: 600; }
input[type=range] { width: 100%; accent-color: #1d3557; }
.stats { font-size: .9rem; margin-bottom: .6rem; color: #1d3557; font-weight: 700; min-height: 1.3em; }
.grid { display: grid; grid-template-columns: repeat(20, 1fr); gap: 2px; margin-bottom: .7rem; }
.cell {
aspect-ratio: 1;
display: flex; align-items: center; justify-content: center;
font-size: .62rem; font-weight: 700; border-radius: 3px;
cursor: pointer; user-select: none; transition: background .12s;
border: 1px solid transparent;
}
.cell.smooth { background: #1d3557; color: #fff; }
.cell.rough { background: #dde3e9; color: #888; }
.cell.selected { outline: 2.5px solid #e63946; outline-offset: 1px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.output { font-size: .88rem; min-height: 1.4em; font-family: ui-monospace, monospace; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Demo: B-smooth number sieve — the first harvesting step in the Number Field Sieve.
// A number n is B-smooth if every prime factor of n is <= B.
// We trial-divide n by primes up to B; if the remainder is 1, n is B-smooth.
const LIMIT = 200;
// Sieve of Eratosthenes up to LIMIT to get small primes
function sievePrimes(max) {
const sieve = new Uint8Array(max + 1).fill(1);
sieve[0] = sieve[1] = 0;
for (let i = 2; i * i <= max; i++) {
if (sieve[i]) for (let j = i * i; j <= max; j += i) sieve[j] = 0;
}
return Array.from({ length: max + 1 }, (_, i) => i).filter(i => sieve[i]);
}
const ALL_PRIMES = sievePrimes(LIMIT);
// Returns true if n is B-smooth (all prime factors <= B)
function isSmooth(n, B) {
let x = n;
for (const p of ALL_PRIMES) {
if (p > B) break;
while (x % p === 0) x = Math.floor(x / p);
}
return x === 1;
}
// Returns the prime factorization of n as a string
function factorize(n) {
const factors = [];
let x = n;
for (const p of ALL_PRIMES) {
if (p * p > x) break;
let exp = 0;
while (x % p === 0) { x = Math.floor(x / p); exp++; }
if (exp > 0) factors.push(exp === 1 ? `${p}` : `${p}^${exp}`);
}
if (x > 1) factors.push(`${x}`);
return factors.join(' × ');
}
let B = 20;
let selected = null;
const slider = document.getElementById('bSlider');
const bVal = document.getElementById('bVal');
const grid = document.getElementById('grid');
const stats = document.getElementById('stats');
const output = document.getElementById('output');
function render() {
bVal.textContent = B;
grid.innerHTML = '';
let smoothCount = 0;
for (let n = 1; n <= LIMIT; n++) {
const smooth = isSmooth(n, B);
if (smooth) smoothCount++;
const cell = document.createElement('div');
cell.className = 'cell ' + (smooth ? 'smooth' : 'rough');
if (n === selected) cell.classList.add('selected');
cell.title = n;
cell.textContent = n;
cell.addEventListener('click', () => {
selected = (selected === n) ? null : n;
render();
if (selected !== null) showFactor(selected);
else output.textContent = '';
});
grid.appendChild(cell);
}
const pct = ((smoothCount / LIMIT) * 100).toFixed(1);
stats.textContent =
fmt('%smoothCount% / %LIMIT% numbers are %B%-smooth (%pct%%)', { smoothCount: smoothCount, LIMIT: LIMIT, B: B, pct: pct });
}
function showFactor(n) {
const smooth = isSmooth(n, B);
const fact = factorize(n);
const largest = Math.max(...fact.split(' × ').map(f => {
const base = parseInt(f.split('^')[0]);
return isNaN(base) ? 1 : base;
}));
output.textContent = fmt('%n% = %fact% → largest prime factor: %largest% %v%', { n: n, fact: fact, largest: largest, v: smooth ? `✓ ${B}-smooth` : `✗ not ${B}-smooth (${largest} > ${B})` });
}
slider.addEventListener('input', () => {
B = parseInt(slider.value);
render();
if (selected !== null) showFactor(selected);
});
document.getElementById('btnFactor').addEventListener('click', () => {
// Pick a random smooth number to highlight
const smoothNums = Array.from({ length: LIMIT }, (_, i) => i + 1)
.filter(n => isSmooth(n, B));
if (smoothNums.length === 0) {
output.textContent = 'No B-smooth numbers found. Try increasing B.';
return;
}
selected = smoothNums[Math.floor(Math.random() * smoothNums.length)];
render();
showFactor(selected);
});
document.getElementById('btnReset').addEventListener('click', () => {
selected = null;
output.textContent = '';
render();
});
render();
<p class="hint">The NFS sieve step hunts for <b>B-smooth numbers</b> — integers whose largest prime factor is ≤ B. Drag the slider to change B and see how many of the first 200 positive integers qualify. More smooth numbers = more "relations" to feed the linear-algebra step.</p>
<div class="controls">
<label for="bSlider">Smoothness bound B = <strong id="bVal">20</strong></label>
<input id="bSlider" type="range" min="2" max="97" value="20" step="1">
</div>
<div class="stats" id="stats"></div>
<div id="grid" class="grid"></div>
<div class="btns">
<button id="btnFactor" type="button">Factor a highlighted number</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div class="output" id="output"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .6rem; }
.controls label { font-size: .92rem; font-weight: 600; }
input[type=range] { width: 100%; accent-color: #1d3557; }
.stats { font-size: .9rem; margin-bottom: .6rem; color: #1d3557; font-weight: 700; min-height: 1.3em; }
.grid { display: grid; grid-template-columns: repeat(20, 1fr); gap: 2px; margin-bottom: .7rem; }
.cell {
aspect-ratio: 1;
display: flex; align-items: center; justify-content: center;
font-size: .62rem; font-weight: 700; border-radius: 3px;
cursor: pointer; user-select: none; transition: background .12s;
border: 1px solid transparent;
}
.cell.smooth { background: #1d3557; color: #fff; }
.cell.rough { background: #dde3e9; color: #888; }
.cell.selected { outline: 2.5px solid #e63946; outline-offset: 1px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.output { font-size: .88rem; min-height: 1.4em; font-family: ui-monospace, monospace; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Demo: B-smooth number sieve — the first harvesting step in the Number Field Sieve.
// A number n is B-smooth if every prime factor of n is <= B.
// We trial-divide n by primes up to B; if the remainder is 1, n is B-smooth.
const LIMIT = 200;
// Sieve of Eratosthenes up to LIMIT to get small primes
function sievePrimes(max) {
const sieve = new Uint8Array(max + 1).fill(1);
sieve[0] = sieve[1] = 0;
for (let i = 2; i * i <= max; i++) {
if (sieve[i]) for (let j = i * i; j <= max; j += i) sieve[j] = 0;
}
return Array.from({ length: max + 1 }, (_, i) => i).filter(i => sieve[i]);
}
const ALL_PRIMES = sievePrimes(LIMIT);
// Returns true if n is B-smooth (all prime factors <= B)
function isSmooth(n, B) {
let x = n;
for (const p of ALL_PRIMES) {
if (p > B) break;
while (x % p === 0) x = Math.floor(x / p);
}
return x === 1;
}
// Returns the prime factorization of n as a string
function factorize(n) {
const factors = [];
let x = n;
for (const p of ALL_PRIMES) {
if (p * p > x) break;
let exp = 0;
while (x % p === 0) { x = Math.floor(x / p); exp++; }
if (exp > 0) factors.push(exp === 1 ? `${p}` : `${p}^${exp}`);
}
if (x > 1) factors.push(`${x}`);
return factors.join(' × ');
}
let B = 20;
let selected = null;
const slider = document.getElementById('bSlider');
const bVal = document.getElementById('bVal');
const grid = document.getElementById('grid');
const stats = document.getElementById('stats');
const output = document.getElementById('output');
function render() {
bVal.textContent = B;
grid.innerHTML = '';
let smoothCount = 0;
for (let n = 1; n <= LIMIT; n++) {
const smooth = isSmooth(n, B);
if (smooth) smoothCount++;
const cell = document.createElement('div');
cell.className = 'cell ' + (smooth ? 'smooth' : 'rough');
if (n === selected) cell.classList.add('selected');
cell.title = n;
cell.textContent = n;
cell.addEventListener('click', () => {
selected = (selected === n) ? null : n;
render();
if (selected !== null) showFactor(selected);
else output.textContent = '';
});
grid.appendChild(cell);
}
const pct = ((smoothCount / LIMIT) * 100).toFixed(1);
stats.textContent =
fmt('%smoothCount% / %LIMIT% numbers are %B%-smooth (%pct%%)', { smoothCount: smoothCount, LIMIT: LIMIT, B: B, pct: pct });
}
function showFactor(n) {
const smooth = isSmooth(n, B);
const fact = factorize(n);
const largest = Math.max(...fact.split(' × ').map(f => {
const base = parseInt(f.split('^')[0]);
return isNaN(base) ? 1 : base;
}));
output.textContent = fmt('%n% = %fact% → largest prime factor: %largest% %v%', { n: n, fact: fact, largest: largest, v: smooth ? `✓ ${B}-smooth` : `✗ not ${B}-smooth (${largest} > ${B})` });
}
slider.addEventListener('input', () => {
B = parseInt(slider.value);
render();
if (selected !== null) showFactor(selected);
});
document.getElementById('btnFactor').addEventListener('click', () => {
// Pick a random smooth number to highlight
const smoothNums = Array.from({ length: LIMIT }, (_, i) => i + 1)
.filter(n => isSmooth(n, B));
if (smoothNums.length === 0) {
output.textContent = 'No B-smooth numbers found. Try increasing B.';
return;
}
selected = smoothNums[Math.floor(Math.random() * smoothNums.length)];
render();
showFactor(selected);
});
document.getElementById('btnReset').addEventListener('click', () => {
selected = null;
output.textContent = '';
render();
});
render();
Slide the bound B and watch how many of the first 200 integers are B-smooth. A tiny B catches almost nothing; a large B catches more numbers but makes the linear-algebra step slower. The real NFS tunes B to the optimal value e(lnN)1/3(lnlnN)2/3 — the sweet spot that produces its famous sub-exponential running time.
This interplay between "collect enough smooth relations" and "keep the factor base manageable" is the core tension in every sieve-based factoring algorithm, from the ancient Quadratic Sieve to the NFS.
Comments
Loading comments...