We use essential cookies to run the site (session, security, and your theme/language preferences). With your permission we also load embedded third-party content, such as YouTube videos. Cookie Policy
When randomness becomes the most reliable calculator
Author(s):Elier Rodríguez García
Index
Introduction
Imagine you need the area of a shape so irregular that no formula exists. You could trace it on graph paper and count squares — but that takes forever for complicated shapes, and becomes completely hopeless in more than two or three dimensions.
Monte Carlo integration offers a different answer: throw random points at a bounding box and count how many land inside your shape. The fraction of hits times the box's area is your estimate, and it gets better with every additional point.
The method is named after the famous casino in Monaco, because it relies on chance the same way gambling does — but unlike gambling, the odds here are firmly in the mathematician's favor. The deeper surprise is the curse of dimensionality: classical grid-based methods collapse when the number of dimensions grows, but Monte Carlo's error shrinks as 1/nregardless of how many dimensions you have. That single fact makes it the method of choice wherever integrals live in high-dimensional spaces.
See also Bayesian inference for another technique where probability meets exact mathematics.
Try It: Estimate π with Darts
A unit circle fits inside a 2×2 square. Any random point (x,y) with x∈[−1,1] and y∈[−1,1] lands inside the circle if x2+y2≤1. The probability of a hit is π/4, so 4 × (hits / total) estimates π.
// Monte Carlo π estimation:
// A unit circle (radius 1) is inscribed in a 2×2 square.
// P(hit) = π·1²/(2·2) = π/4 => π ≈ 4 · hits/total
// Var(4·X) = 16·Var(X) = 16·(π/4)·(1-π/4) ≈ 2.696
// RMS error for π estimate = 4·σ/√n ≈ 1.642/√n — halve error, need 4× more points.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const conv = document.getElementById('conv');
const cctx = conv.getContext('2d');
const W = canvas.width, H = canvas.height;
const CX = W/2, CY = H/2;
const R = Math.min(W, H)/2 - 4; // pixel radius for drawing
let total = 0, hits = 0;
let autoId = null;
const errorHistory = [];
const MAX_HISTORY = 200;
function mapX(x) { return CX + x * R; } // x in [-1,1] -> pixel
function mapY(y) { return CY - y * R; } // y in [-1,1] -> pixel (flip)
function drawBackground() {
ctx.clearRect(0, 0, W, H);
// square border
ctx.strokeStyle = '#adb1b8'; ctx.lineWidth = 1;
ctx.strokeRect(CX - R, CY - R, 2*R, 2*R);
// circle
ctx.beginPath(); ctx.arc(CX, CY, R, 0, 2*Math.PI);
ctx.strokeStyle = '#1d3557'; ctx.lineWidth = 1.5; ctx.stroke();
}
function addDarts(n) {
drawBackground();
// re-draw all existing darts (just redraw from scratch using tiny dots)
// For performance we draw only the NEW darts on top of whatever is there
// but since we clear on background, we track points separately (compressed):
// Actually simpler: just draw new darts; background + circle are redrawn each call.
for (let i = 0; i < n; i++) {
const x = Math.random() * 2 - 1; // [-1, 1]
const y = Math.random() * 2 - 1;
const inside = x*x + y*y <= 1;
if (inside) hits++;
total++;
ctx.fillStyle = inside ? 'rgba(10,125,51,0.55)' : 'rgba(200,60,60,0.45)';
ctx.beginPath();
ctx.arc(mapX(x), mapY(y), 1.5, 0, 2*Math.PI);
ctx.fill();
}
updateStats();
}
function updateStats() {
const piEst = total === 0 ? NaN : 4 * hits / total;
const err = total === 0 ? NaN : Math.abs(piEst - Math.PI);
document.getElementById('total').textContent = total.toLocaleString();
document.getElementById('inside').textContent = hits.toLocaleString();
document.getElementById('pi-est').textContent = total === 0 ? '—' : piEst.toFixed(5);
document.getElementById('error').textContent = total === 0 ? '—' : err.toFixed(5);
if (total > 0) {
errorHistory.push({ n: total, e: err });
if (errorHistory.length > MAX_HISTORY) errorHistory.shift();
drawConvergence();
}
}
function drawConvergence() {
const cW = conv.width, cH = conv.height;
cctx.clearRect(0, 0, cW, cH);
if (errorHistory.length < 2) return;
const maxErr = Math.max(...errorHistory.map(p => p.e), 0.01);
const minN = errorHistory[0].n;
const maxN = errorHistory[errorHistory.length-1].n;
const rangeN = Math.max(maxN - minN, 1);
// Draw 1/sqrt(n) reference curve
cctx.beginPath();
cctx.strokeStyle = '#adb1b8';
cctx.lineWidth = 1;
cctx.setLineDash([3, 3]);
for (let i = 0; i < errorHistory.length; i++) {
const p = errorHistory[i];
const px = (p.n - minN) / rangeN * (cW - 10) + 5;
const refErr = 1.642 / Math.sqrt(p.n);
const py = cH - (Math.min(refErr, maxErr) / maxErr) * (cH - 8) - 4;
i === 0 ? cctx.moveTo(px, py) : cctx.lineTo(px, py);
}
cctx.stroke();
cctx.setLineDash([]);
// Draw actual error
cctx.beginPath();
cctx.strokeStyle = '#1d3557';
cctx.lineWidth = 1.5;
for (let i = 0; i < errorHistory.length; i++) {
const p = errorHistory[i];
const px = (p.n - minN) / rangeN * (cW - 10) + 5;
const py = cH - (Math.min(p.e, maxErr) / maxErr) * (cH - 8) - 4;
i === 0 ? cctx.moveTo(px, py) : cctx.lineTo(px, py);
}
cctx.stroke();
// Legend
cctx.font = '9px system-ui, sans-serif';
cctx.fillStyle = '#1d3557';
cctx.fillText('actual error', 6, 10);
cctx.fillStyle = '#adb1b8';
cctx.fillText('1/√n reference', 6, 21);
}
function reset() {
stopAuto();
total = 0; hits = 0;
errorHistory.length = 0;
ctx.clearRect(0, 0, W, H);
drawBackground();
cctx.clearRect(0, 0, conv.width, conv.height);
document.getElementById('total').textContent = '0';
document.getElementById('inside').textContent = '0';
document.getElementById('pi-est').textContent = '—';
document.getElementById('error').textContent = '—';
}
function stopAuto() {
if (autoId !== null) {
clearInterval(autoId);
autoId = null;
document.getElementById('btnAuto').classList.remove('active');
document.getElementById('btnAuto').textContent = 'Auto';
}
}
document.getElementById('btn10').addEventListener('click', () => addDarts(10));
document.getElementById('btn100').addEventListener('click', () => addDarts(100));
document.getElementById('btn1000').addEventListener('click', () => addDarts(1000));
document.getElementById('btnReset').addEventListener('click', reset);
document.getElementById('btnAuto').addEventListener('click', () => {
if (autoId !== null) {
stopAuto();
} else {
autoId = setInterval(() => addDarts(50), 120);
document.getElementById('btnAuto').classList.add('active');
document.getElementById('btnAuto').textContent = 'Stop';
}
});
drawBackground();
// Verify core claim in console for testing:
// After N darts: piEst ≈ π with RMS error ≈ 0.738/sqrt(N)
console.log('Monte Carlo π demo loaded. Core: π ≈ 4·(hits inside unit circle)/(total darts).');
console.log('Theoretical RMS error after n darts: ~1.642/sqrt(n). E.g. n=1000 ->', (1.642/Math.sqrt(1000)).toFixed(5));
Result
Console
Watch the estimate converge as darts accumulate. The error does not shrink uniformly — it bounces around and occasionally gets worse before it improves. That is the nature of randomness: statistically the error is proportional to 1/n, meaning you need 100× more darts to gain one extra decimal place. But the method never cares whether you are working in 2 dimensions or 200.
The Real Complexity
How good is Monte Carlo integration, really? The answer depends on what you compare it against.
Grid-based methods (the trapezoid rule, Simpson's rule, Gaussian quadrature) divide the integration domain into a regular grid and evaluate the function at grid points. In d dimensions, covering the domain with k points per axis requires kd evaluations — an exponential blowup. A 10-point grid in 10 dimensions needs 10 billion evaluations; in 20 dimensions, 1020.
Monte Carlo does not suffer this curse. Its mean squared error is:
MSE=nVar(f)
which means the root-mean-square error is σ/n, regardless of the number of dimensions d. To halve the error, double the number of samples. To gain one decimal digit, multiply by 100. The constant σ (the standard deviation of f over the domain) matters, but the dimension d does not appear anywhere.
Variance reduction techniques can push the convergence faster in practice:
Importance sampling: sample more densely where f is large.
Stratified sampling: divide the domain into strata and sample each separately.
Quasi-Monte Carlo: replace pseudo-random points with low-discrepancy sequences (Halton, Sobol) to achieve near-O(n−1) convergence.
Monte Carlo integration is provably optimal among methods that treat the integrand as a black box in high dimensions. It is not special-cased for specific function shapes — it works on any integrand you can evaluate, including probability distributions that can only be sampled, not written down.
Where It Matters
Any problem that boils down to "evaluate a high-dimensional integral" ends up using Monte Carlo:
Physics simulations: particle transport (neutron scattering, radiation therapy planning) requires integrating over all possible paths through matter — a space with hundreds of dimensions. Monte Carlo was invented at Los Alamos in 1946 by Stanislaw Ulam and John von Neumann precisely for this problem.
Financial derivatives pricing: the fair price of an option depends on the expected payoff under a probability distribution over future asset paths. A basket option on 50 correlated assets lives in a 50-dimensional space — only Monte Carlo (and quasi-Monte Carlo) is practical.
Bayesian posterior inference: computing a posterior distribution requires integrating the likelihood times the prior over all parameter values. Markov Chain Monte Carlo (MCMC) is the engine behind almost all modern Bayesian computation.
Computer graphics: realistic rendering traces light paths through a scene. Each pixel color is an integral over all possible paths from the camera to light sources — an infinite-dimensional space. Path tracing (used in films and real-time ray tracing) is Monte Carlo integration applied to light transport.
Machine learning: stochastic gradient descent is a Monte Carlo approximation of the true gradient, and variational inference replaces intractable posteriors with Monte Carlo estimates.
The method also connects to randomized algorithms and the broader theme that randomness, used carefully, is a powerful computational resource.
Conclusion
Monte Carlo integration started as a trick for nuclear physicists who could not solve their equations analytically. It has since become one of the most widely used algorithms in science, finance, and engineering.
Its secret is geometric: in high dimensions, almost all the volume of a hypercube sits near its surface, and regular grids miss most of it. Random points, by contrast, are distributed uniformly everywhere, so nothing is systematically neglected.
The price is statistical noise — the 1/n convergence rate is modest. But it is guaranteed, dimension-independent, and improvable with variance reduction. For integrals in ten or more dimensions, no deterministic method comes close.
Next time you watch a ray-traced film, price an option, or train a neural network, there is a good chance a shower of random darts is quietly doing the mathematics behind the scenes.
Comments
Loading comments...