OS-003 · Monte Carlo

Buffon's Needle Simulation

Drop a needle on a floor ruled with parallel lines. The chance that it lands across a line involves π — so counting crossings over many drops estimates π. Posed by Buffon in 1733, it is the oldest problem in geometric probability and an early Monte Carlo experiment.

Interactive model

OS-003 · buffons-needle · seeded
crosses a linemisseslines, spacing t
running estimateπ±2 standard errors
Parameters
Short needle: ℓ ≤ t
How the random direction is drawn
Same seed, same run — here, in simulation.js and in simulation.py.
Crossing needles H
2,540
Fraction crossing H/n
0.5080
Theory 2ℓ/(πt)
0.5093
Estimate of π
3.1496
Error |π̂ − π|
0.0080
Standard error
0.0438

Example run: default parameters, seed 42.

Where π comes from

Let the lines be a distance t apart and the needle have length ℓ ≤ t. Two random numbers describe a drop: the distance D from the needle's center to the nearest line, uniform on [0, t/2], and the acute angle θ between needle and lines, uniform on [0, π/2]. The needle reaches the line when D ≤ (ℓ/2)·sin θ. Averaging that chance over the angle:

P(cross) = ∫0π/2 (ℓ sin θ / t) · (2/π) dθ = 2ℓ / (πt)

π enters through the angle: the needle points in every direction equally, and the average of sin θ over directions is 2/π. With H crossings in n drops, solving for π gives the estimator

π̂ = 2ℓn / (tH)

Because π̂ divides by a random count, its error is not simply binomial. The delta method gives SE(π̂) ≈ π̂·√((1 − p̂)/H) with p̂ = H/n. Even with the longest needle allowed here (ℓ = t) the standard error is about 2.37/√n, against 1.64/√n for the random points in a square method — Buffon's experiment is the less efficient way to get π. Shorter needles cross less often and make it worse; drag ℓ/t down and watch the band widen.

Does the simulation cheat by using π?

A real needle needs no π: gravity and a spin do the randomizing. A computer has to generate the direction, and the obvious way — θ = π·U — uses the value of π to estimate π. The estimate is still a fair statistical experiment, but it is circular as a way of discovering π. The Needle direction control offers both options:

  • Point in half-disc (default). Draw (u, v) uniformly in the rectangle [−1, 1) × [0, 1) and reject it until it falls inside the unit half-disc. The direction of an accepted point is uniform on [0, π) by rotational symmetry, and sin θ = v/√(u² + v²) needs only a square root. No value of π goes into placing a needle; the code uses π only afterwards, to report the error.
  • Angle θ ~ U[0, π). The textbook shortcut, with π supplied by the programmer.

The two give statistically identical results. There is a twist: the rejection step accepts a point with probability π/4, so the computer is quietly running the dartboard estimate of π just to point the needles.

Real needles, real experiments

Georges-Louis Leclerc, Comte de Buffon, posed the problem in 1733 and published the solution in 1777. In the nineteenth century several people dropped physical needles to test it. The best-known result is Mario Lazzarini's from 1901: with ℓ/t = 5/6 he reported 1,808 crossings in 3,408 drops, which gives 355/113 ≈ 3.1415929, correct to six decimal places. At that sample size the standard error is about 0.05, so an error below 10−6 is not believable. The counts give it away: 3,408 = 16 × 213 and 1,808 = 16 × 113, exactly the multiples that turn 2ℓn/(tH) into 355/113 — the result of stopping, or selecting, at a lucky moment. Run the lab with 3,400 needles and a few seeds to see what honest scatter looks like.

Example result

With ℓ/t = 0.8, 5,000 needles and seed 42, 2,540 needles crossed a line — a fraction of 0.5080 against the theoretical 0.5093. That gives π̂ = 3.1496, an error of 0.0080 with a standard error of 0.0438.

Output of buffons-needle with needle_length=0.8 n=5000 angle=disc seed=42
Crossing needles H2,540
Fraction crossing H/n0.5080
Theory 2ℓ/(πt)0.5093
Estimate of π3.1496
Error |π̂ − π|0.0080
Standard error0.0438

Implementation

The page runs simulation.js from the package below; the Python file is a line-by-line port with the same random-number generator, so a given seed produces the same result in both languages. The excerpts show the core of each.

JavaScript — simulation.js
// Drop one needle: centre uniform on the floor, direction uniform on [0, π).
// 'disc' draws the direction by rejection from the upper half of the unit disc,
// so π never appears in the code; 'angle' draws θ directly and needs π to do so.
export function dropNeedle(len, method, rng) {
  const x = FLOOR_W * rng.uniform();
  const y = STRIPS * rng.uniform();
  let c, s;
  if (method === 'angle') {
    const theta = Math.PI * rng.uniform();
    c = Math.cos(theta);
    s = Math.sin(theta);
  } else {
    let u, v, r2;
    do {
      u = 2 * rng.uniform() - 1;
      v = rng.uniform();
      r2 = u * u + v * v;
    } while (r2 > 1 || r2 === 0);
    const r = Math.sqrt(r2);
    c = u / r;
    s = v / r;
  }
  // The needle spans y ± (ℓ/2)·sin θ; it crosses a line when both ends lie in different strips.
  const h = (len / 2) * s;
  const hit = Math.floor(y - h) !== Math.floor(y + h);
  return { x, y, c, s, hit };
}

// P(cross) = 2ℓ/(πt), so π ≈ 2ℓn/(tH). Delta-method standard error: π̂·√((1 − p̂)/H).
export function summarize(len, n, hits) {
  const p = hits / n;
  const estimate = hits > 0 ? (2 * len * n) / hits : null;
  return {
    needles: n,
    crossings: hits,
    cross_fraction: p,
    cross_theory: (2 * len) / Math.PI,
    estimate,
    error: estimate === null ? null : Math.abs(estimate - Math.PI),
    std_error: estimate === null ? null : estimate * Math.sqrt((1 - p) / hits),
  };
}
Python — simulation.py
def drop_needle(length, method, rng):
    """Centre uniform on the floor, direction uniform on [0, pi). Returns True if it crosses a line."""
    x = FLOOR_W * rng.uniform()  # horizontal position: irrelevant to crossing, kept for drawing
    y = STRIPS * rng.uniform()
    if method == "angle":
        s = math.sin(math.pi * rng.uniform())
    else:
        # rejection from the upper half of the unit disc: no pi needed
        while True:
            u = 2 * rng.uniform() - 1
            v = rng.uniform()
            r2 = u * u + v * v
            if not (r2 > 1 or r2 == 0):
                break
        s = v / math.sqrt(r2)
    h = length / 2 * s
    return math.floor(y - h) != math.floor(y + h)


def estimate_pi(length, n, hits):
    """P(cross) = 2l/(pi t) with t = 1, so pi ~ 2 l n / H; delta-method standard error."""
    p = hits / n
    est = 2 * length * n / hits
    return est, est * math.sqrt((1 - p) / hits)

Run the full model from a terminal:

Shell
node simulation.js --needle-length 0.8 --n 5000 --seed 42
python simulation.py --needle-length 0.8 --n 5000 --seed 42

Download the model

buffons-needle.zip6 files · 11.3 KBDownload .zip
Files in buffons-needle.zip
FileContentsSize
README.mdHow to run it, parameters, example result2.3 KB
model.jsonParameters with ranges and defaults, outputs, the seed-42 example2.0 KB
simulation.jsThe model used on this page plus a command-line runner (Node 18+)7.4 KB
simulation.pyPython 3.9+ port, standard library only6.5 KB
example.csvResult table from the example run5.4 KB
LICENSE.txtMIT License1.0 KB

Assumptions & limitations

  • Short needles only. The formula 2ℓ/(πt) holds for ℓ ≤ t. Longer needles can cross several lines and need a different formula, so the lab caps ℓ at t.
  • Uniform position and direction. The center is uniform over the floor and the direction uniform over half a turn, independently. Real throws are only approximately like this; bias in either breaks the formula.
  • Thin, straight needles and exact lines. Needles have no width, and a needle that exactly touches a line is an event of probability zero.
  • Delta-method error. The standard error linearizes 1/H; it is poor when there are only a few crossings, and there is no estimate at all when H = 0.
  • Pseudo-random numbers. Drops come from a seeded generator; see pseudo-random numbers and seeds.

References

  1. Grinstead, C. M. & Snell, J. L. Introduction to Probability, 2nd rev. ed., chapter 2. American Mathematical Society. math.dartmouth.edu/~prob/prob/prob.pdf
  2. Metropolis, N. & Ulam, S. (1949). The Monte Carlo Method. Journal of the American Statistical Association, 44(247), 335–341. doi.org/10.1080/01621459.1949.10483310