Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 88, Buffon’s Needle: a still of the monte carlo / needle drop plate as the atlas renders it, in the lattices accent.

PL. 88  ·  LATTICES / MONTE CARLO / NEEDLE DROP

Buffon’s Needle

Georges-Louis Leclerc, Comte de Buffon, 1777 · Pierre-Simon Laplace, 1812

OPEN THE LIVE PLATE ▸

DEFINITION

P(cross) = 2ℓ/(πt),  ℓ ≤ t
after N drops, H hits:  π ≈ 2ℓN / (tH)

NOTES

Rule a floor with parallel lines spaced t apart and drop a needle of length ℓ ≤ t onto it, over and over, at a uniform position and a uniform angle. The chance any one needle crosses a line is exactly 2ℓ/(πt) — a probability with no circle drawn anywhere in the setup, only the arc a needle’s own rotation sweeps through. Buffon posed it as a diversion inside an essay on the fairness of games, not as a way to compute π; running the crossing count back through that formula to estimate π is what later readers did with it, and it is usually credited as the first Monte Carlo method on record, a century and a half before the name. The convergence is a genuine law-of-large-numbers convergence and nothing faster — watch the running estimate overshoot, undershoot, and wander for a long time before it settles. The vertical trace is that estimate, newest at the bottom, against a rule at π and the ±0.05 band the plate reports reaching.

PROVENANCE

Origin
G.-L. Leclerc, Comte de Buffon, “Essai d’arithmétique morale”, in Histoire naturelle, générale et particulière, Supplément, tome 4, 1777 — he had put the question to the Académie royale des sciences as early as 1733
Standing
Public domain — 18th-century geometric probability
Generalisation
P.-S. Laplace, Théorie analytique des probabilités, 1812, extended Buffon’s single family of lines to a rectangular grid of two perpendicular rulings and set out the reverse calculation — crossings back to π — as a deliberate method. This plate keeps to Buffon’s original single ruling, not Laplace’s two-directional grid
Constants
ℓ/t is capped at 1: past that a needle can straddle two lines in a way the simple formula does not cover, and the crossing probability needs the longer compound expression Laplace derived for the long-needle case — a different, later result, out of scope here. The floor is a measurement rather than a bound: the crossing rate is 2ℓ/(πt), so a shorter needle crosses less often and the estimate takes proportionally longer to arrive, and below ℓ/t = 0.4 that wait stops being a wait and becomes an absence
Sampling
The nine rulings sit at the half-spacings, not at the multiples of one. The estimator is unbiased only if a drop’s distance to the nearest line is uniform on [0, t/2], which needs the band the centres fall in to be a whole number of periods — offset by half, nine lines cover the plate exactly and every line the count uses is drawn. On the multiples the arithmetic is just as uniform, but the two lines at the very top and bottom edges would be counted and never seen

HOUDINI · VEX

The same published mathematics as a Detail Wrangle body. Paste it into a Wrangle with Run Over set to Detail; every constant is the published value plus a tweak channel, so Create Spare Parameters gives a slider that starts where the paper does.

// FORMA — PL. 88 · BUFFON’S NEEDLE — Georges-Louis Leclerc, Comte de Buffon, 1777 · Pierre-Simon Laplace, 1812
//   P(cross) = 2ℓ/(πt),  ℓ ≤ t
//   after N drops, H hits:  π ≈ 2ℓN / (tH)
// Paste into a Detail Wrangle (Run Over: Detail), no inputs needed.
// Written from the published mathematics, not adapted from any code.
// Constants arrive at their published values. Press the node's Create
// Spare Parameters button and every tweak becomes a slider — starting
// at 0, the published figure, and moving in the constant's own units.
// https://forma-gen.com/#plate=buffon

float p_ratio = 0.85 + chf('ratio_tweak');    // ℓ/t — needle length ÷ line spacing · live 0.4 .. 1
float p_drops = 6 + chf('drops_tweak');       // drops per shot · live 3 .. 20

// The plate's own colour: FORMA's LATTICES accent as a cosine ramp,
// brightest near t = 0 and t = 1, near-black around t = 0.5.
vector forma_ramp(float t){
  return set(
    0.46 + 0.5 * cos(6.28318530718 * (t + 0)),
    0.4185 + 0.4549 * cos(6.28318530718 * (t + 0.05)),
    0.1389 + 0.151 * cos(6.28318530718 * (t + 0.1)));
}

// The experiment, run to the plate's own equilibrium sheet in one cook:
// needles of length ℓ = ratio·t dropped at uniform position and angle onto
// nine rulings, hits told from misses by the distance from centre to the
// nearest line — d ≤ (ℓ/2)·sin θ, the short-needle case exactly. The sheet
// holds batch × fade-period / decay needles on the plate (≈ 780 at the
// default batch), so that is how many this cook drops; the running estimate
// 2ℓN/(tH) is emitted as the plate's own vertical trace, newest at the
// bottom, beside the rule at π and the ±0.05 band. Deterministic:
// random(counted seed) on the plate's own seed, so every cook is the same
// rain of needles.
int forma_lines = 9;
int forma_hist  = 900;

float W = 560.0, H = 560.0;
float spacing = H / float(forma_lines);
float needle = p_ratio * spacing;
int total = int(rint(p_drops * 30.0 / 0.23));   // the plate's own equilibrium: batch × FADE / decay

// the floor: the fixed apparatus the needles fall onto
vector fc = forma_ramp(0.0);
for (int i = 0; i < forma_lines; i++){
    float y = (float(i) + 0.5) * spacing;
    int l0 = addpoint(0, set(-W / 2.0, (H / 2.0) - y, 0.0));
    int l1 = addpoint(0, set( W / 2.0, (H / 2.0) - y, 0.0));
    setpointattrib(0, "Cd", l0, fc);  setpointattrib(0, "Cd", l1, fc);
    setpointattrib(0, "Alpha", l0, 0.22);  setpointattrib(0, "Alpha", l1, 0.22);
    addprim(0, "polyline", l0, l1);
}

vector missC = forma_ramp(0.84), hitC = forma_ramp(0.98);
int rc = 47;                      // the plate's own seed, counted upward
int drops = 0, hitsN = 0;
float est[];
for (int i = 0; i < total; i++){
    float cx = random(rc) * W;  rc++;
    float cy = random(rc) * H;  rc++;
    float th = random(rc) * M_PI;  rc++;       // 0..π — a needle has no head
    float hx = cos(th) * needle / 2.0;
    float hy = sin(th) * needle / 2.0;         // ≥ 0 on 0..π, its own |·|
    float d = abs(cy - (rint(cy / spacing - 0.5) + 0.5) * spacing);
    drops++;
    int hit = d <= hy ? 1 : 0;
    if (hit) hitsN++;
    vector col = hit ? hitC : missC;
    float al = hit ? 0.6 : 0.26;
    // canvas y runs down; negated so the sheet lies as the plate shows it
    int p0 = addpoint(0, set(cx - hx - W / 2.0, (H / 2.0) - (cy - hy), 0.0));
    int p1 = addpoint(0, set(cx + hx - W / 2.0, (H / 2.0) - (cy + hy), 0.0));
    setpointattrib(0, "Cd", p0, col);  setpointattrib(0, "Cd", p1, col);
    setpointattrib(0, "Alpha", p0, al);  setpointattrib(0, "Alpha", p1, al);
    addprim(0, "polyline", p0, p1);
    if (hitsN > 0) push(est, 2.0 * needle * float(drops) / (spacing * float(hitsN)));
}

// the estimate's trace: the last 900 samples against the rule at π and the
// ±0.05 band — vertical against the horizontal rulings, as the plate argues
float SPAN = 0.5;
vector rl = forma_ramp(0.0);
for (int b = 0; b < 3; b++){
    float v = b == 0 ? M_PI : (b == 1 ? M_PI - 0.05 : M_PI + 0.05);
    float x = clamp((v - M_PI) / SPAN, -1.0, 1.0) * (W / 2.0);
    int r0 = addpoint(0, set(x, -H / 2.0, 0.0));
    int r1 = addpoint(0, set(x,  H / 2.0, 0.0));
    setpointattrib(0, "Cd", r0, rl);  setpointattrib(0, "Cd", r1, rl);
    setpointattrib(0, "Alpha", r0, b == 0 ? 0.34 : 0.16);
    setpointattrib(0, "Alpha", r1, b == 0 ? 0.34 : 0.16);
    addprim(0, "polyline", r0, r1);
}
int nh = min(len(est), forma_hist);
if (nh > 1){
    vector tc = forma_ramp(0.98);
    int prim = addprim(0, "polyline");
    for (int i = 0; i < nh; i++){
        float v = est[len(est) - nh + i];
        float x = clamp((v - M_PI) / SPAN, -1.0, 1.0) * (W / 2.0);
        // newest at the bottom, as the plate pins it
        float y = (H / 2.0) - float(i) / float(nh - 1) * H;
        int pt = addpoint(0, set(x, y, 0.0));
        setpointattrib(0, "Cd", pt, tc);
        setpointattrib(0, "Alpha", pt, 0.9);
        addvertex(0, prim, pt);
    }
}