Here is the most famous randomized trick of all. Throw darts at random into a square, and check how many land inside the circle that just fits in it. The fraction inside is the ratio of the circle's area to the square's — which works out to π/4. Multiply by four and you have estimated π with nothing but a random number generator.
<p class="hint">Throw random darts into the square. Each dart that lands inside the circle counts toward an estimate of <b>π</b>: the fraction inside is π/4, so 4 × (inside / total) ≈ π. Few darts → jittery guess. Many darts → it settles near 3.14159. That is a <b>Monte Carlo</b> algorithm.</p>
<canvas id="board" width="260" height="260"></canvas>
<div class="readout">
<div><span class="lbl">Darts</span><span id="total">0</span></div>
<div><span class="lbl">Inside</span><span id="inside">0</span></div>
<div><span class="lbl">π estimate</span><span id="pi">—</span></div>
<div><span class="lbl">Error</span><span id="err">—</span></div>
</div>
<div class="btns">
<button id="throw10" type="button">Throw 10</button>
<button id="throw1k" type="button">Throw 1000</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
#board { border: 1px solid #cdd9e3; border-radius: 8px; background: #f5f8fb; display: block; }
.readout { display: grid; grid-template-columns: repeat(2, minmax(120px, 1fr)); gap: .35rem .9rem;
margin: .7rem 0; font: 600 15px ui-monospace, monospace; }
.readout div { display: flex; justify-content: space-between; border-bottom: 1px solid #eef2f6; padding: .2rem 0; }
.lbl { color: #5a7088; font-weight: 600; }
#pi { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
// Monte Carlo estimate of pi. Throw darts uniformly in a 1x1 square; a dart at (x,y) is "inside" the unit quarter-circle iff x*x + y*y <= 1. In the limit the fraction inside equals (area of quarter circle)/(area of square) = (pi/4)/1, so pi ~= 4 * inside / total. This is the canonical randomized (Monte Carlo) algorithm: always fast, only approximately correct, error ~ 1/sqrt(total).
const cv = document.getElementById('board');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
let total = 0, inside = 0;
function drawFrame(){
ctx.clearRect(0, 0, W, H);
// quarter-circle guide (radius = full width, centered at bottom-left)
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.arc(0, H, W, -Math.PI/2, 0); ctx.stroke();
}
function plot(x, y, isIn){
// map unit square (0..1, 0..1) to canvas with y flipped (origin bottom-left)
const px = x * W, py = H - y * H;
ctx.fillStyle = isIn ? '#0a7d33' : '#e63946';
ctx.fillRect(px, py, 2, 2);
}
function refresh(){
document.getElementById('total').textContent = total;
document.getElementById('inside').textContent = inside;
if (total === 0){
document.getElementById('pi').textContent = '—';
document.getElementById('err').textContent = '—';
return;
}
const est = 4 * inside / total;
document.getElementById('pi').textContent = est.toFixed(5);
document.getElementById('err').textContent = Math.abs(est - Math.PI).toFixed(5);
}
function throwDarts(n){
for (let i = 0; i < n; i++){
const x = Math.random(), y = Math.random();
const isIn = (x*x + y*y) <= 1;
total++; if (isIn) inside++;
plot(x, y, isIn);
}
refresh();
}
function reset(){ total = 0; inside = 0; drawFrame(); refresh(); }
document.getElementById('throw10').addEventListener('click', () => throwDarts(10));
document.getElementById('throw1k').addEventListener('click', () => throwDarts(1000));
document.getElementById('reset').addEventListener('click', reset);
reset();
<p class="hint">Throw random darts into the square. Each dart that lands inside the circle counts toward an estimate of <b>π</b>: the fraction inside is π/4, so 4 × (inside / total) ≈ π. Few darts → jittery guess. Many darts → it settles near 3.14159. That is a <b>Monte Carlo</b> algorithm.</p>
<canvas id="board" width="260" height="260"></canvas>
<div class="readout">
<div><span class="lbl">Darts</span><span id="total">0</span></div>
<div><span class="lbl">Inside</span><span id="inside">0</span></div>
<div><span class="lbl">π estimate</span><span id="pi">—</span></div>
<div><span class="lbl">Error</span><span id="err">—</span></div>
</div>
<div class="btns">
<button id="throw10" type="button">Throw 10</button>
<button id="throw1k" type="button">Throw 1000</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
#board { border: 1px solid #cdd9e3; border-radius: 8px; background: #f5f8fb; display: block; }
.readout { display: grid; grid-template-columns: repeat(2, minmax(120px, 1fr)); gap: .35rem .9rem;
margin: .7rem 0; font: 600 15px ui-monospace, monospace; }
.readout div { display: flex; justify-content: space-between; border-bottom: 1px solid #eef2f6; padding: .2rem 0; }
.lbl { color: #5a7088; font-weight: 600; }
#pi { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
// Monte Carlo estimate of pi. Throw darts uniformly in a 1x1 square; a dart at (x,y) is "inside" the unit quarter-circle iff x*x + y*y <= 1. In the limit the fraction inside equals (area of quarter circle)/(area of square) = (pi/4)/1, so pi ~= 4 * inside / total. This is the canonical randomized (Monte Carlo) algorithm: always fast, only approximately correct, error ~ 1/sqrt(total).
const cv = document.getElementById('board');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
let total = 0, inside = 0;
function drawFrame(){
ctx.clearRect(0, 0, W, H);
// quarter-circle guide (radius = full width, centered at bottom-left)
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.arc(0, H, W, -Math.PI/2, 0); ctx.stroke();
}
function plot(x, y, isIn){
// map unit square (0..1, 0..1) to canvas with y flipped (origin bottom-left)
const px = x * W, py = H - y * H;
ctx.fillStyle = isIn ? '#0a7d33' : '#e63946';
ctx.fillRect(px, py, 2, 2);
}
function refresh(){
document.getElementById('total').textContent = total;
document.getElementById('inside').textContent = inside;
if (total === 0){
document.getElementById('pi').textContent = '—';
document.getElementById('err').textContent = '—';
return;
}
const est = 4 * inside / total;
document.getElementById('pi').textContent = est.toFixed(5);
document.getElementById('err').textContent = Math.abs(est - Math.PI).toFixed(5);
}
function throwDarts(n){
for (let i = 0; i < n; i++){
const x = Math.random(), y = Math.random();
const isIn = (x*x + y*y) <= 1;
total++; if (isIn) inside++;
plot(x, y, isIn);
}
refresh();
}
function reset(){ total = 0; inside = 0; drawFrame(); refresh(); }
document.getElementById('throw10').addEventListener('click', () => throwDarts(10));
document.getElementById('throw1k').addEventListener('click', () => throwDarts(1000));
document.getElementById('reset').addEventListener('click', reset);
reset();
Throw a handful of darts and the estimate is rough and jittery. Throw thousands and it settles near 3.14159. This is a Monte Carlo algorithm in its purest form: each answer is probably close, and the more random samples you take, the tighter it gets. The error shrinks with the square root of the number of darts — never exact, but as accurate as you are patient.
Comments
Loading comments...