![]() |
Buffon's Needles | Lab Report |
Purpose
The purpose of this project is to explore a Monte Carlo
method of estimating the value of pi (p),
which was first discovered by Count Buffon in 1733.
Mathematics Background
Take a number of toothpicks and randomly throw them on a tablecloth, a hardwood floor, a
brick sidewalk, or on anything that has a number of equally spaced parallel lines. The
parallel lines must be at least as far apart as the length of a toothpick. Count the
number of tosses and the number of toothpicks that cross the parallel lines. The following
formula gives you an estimate for p:

If you can find some parallel lines that are exactly as far apart as the length of a toothpick, the formula simplifies to:

This is the equation well use for the example in the Figure 1:

Figure 1. Tossing Toothpicks to Estimate the Value of Pi
Figure 1 shows 7 tosses and 4 crossings, the formula tells us:
p » 2 ´ 7 / 4 = 3.5
So our estimate is in the right neighborhood its close to 3.141592653589793... Can we just keep throwing more toothpicks and get a more precise answer? Not really. You can use more precision on your calculator, but youll only ever be accurate to a few decimal digits with this method. Nevertheless, this "Monte Carlo" method of estimating a value is still useful for certain kinds of scientific calculations. Molecular chemists can use this method on a computer instead of beakers to explore how molecules react. Nuclear engineers studying how a nuclear reactor works can use Monte Carlo calculations to answer questions that have no other answers.
How can throwing toothpicks on parallel lines possibly be used to estimate p? Can you believe that Count Buffon first experimented with this method in the 1733 when he dropped needles onto parallel lines?
Consider the geometry of the toothpicks when tossed onto the parallel lines in the Figure 2:

Figure 2. Geometry of Toothpicks Tossed Onto Parallel Lines
In the diagram above, the parallel lines are all W units apart. The toothpicks are all L units long. Every time a toothpick is thrown onto the parallel lines, lets take a look at two values, called D and angle q:
The value H shown in Figure 2 can be computed from knowledge of L and q:
H = (L/2) sin q
When D £ H, that is, whenever the distance of the midpoint of the toothpick to the closest parallel line is less than H, the toothpick crosses a line. When D > H the toothpick does not cross a line.
As we throw toothpicks, we can form a dot plot of D versus q. We plot points that cross a line in red and points that do not cross a line in black. Figure 3 is such a dot plot from a computer simulation of 100,000 tosses:

Figure 3. Plot of D versus q for 100,000 Tosses
(Using Method 3 -- see below)
The top part of Figure 3 explains how to compute the probability of a line crossing a parallel line. This probability is simply the ratio of the Number Crossing a Parallel Line to the Number Tossed. This value can be computed by simply observing the number of toothpicks tossed and the number that cross one of the parallel lines.
But this ratio of crossings versus tosses is also mathematically the ratio of the red area above to the area of the rectangle. That is:

The Red area can computed using some calculus:

The Area of the Rectangle is much simpler to compute: Area of Rectangle = width x height = p x W/2.

Rearrangement of this equation gives us the equation we want:

Or, when L = W:

See "The Monte Carlo Method to approximate Pi " for a different Monte Carlo method for calculating p: www.daimi.aau.dk/~u951581/pi/MonteCarlo/pimc.html . (For additional links about p see efg's Math page).
Materials and Equipment
Software Requirements
Windows 95/98/NT
Delphi 3 or 4 (to recompile)
Buffon.EXE programHardware Requirements
VGA display
Procedure
Results
Estimates of p for Various Random
Direction Generation Methods
(166 MHz Pentium)
Graphics Checked (Enabled) |
||||
| Method | 1,000 tosses | Time [sec] | 100,000 tosses | Time [sec] |
| 1 | 3.044140 | 0.289 | 3.083374 | 19.048 |
| 2 | 3.044140 | 0.305 | 3.128715 | 19.310 |
| 3 | 3.100775 | 0.302 | 3.125244 | 18.293 |
| 4 | 3.149606 | 0.288 | 3.139570 | 18.828 |
Graphics Not Checked (Disabled) |
||||
| Method | 1,000,000 tosses | Time [sec] | 10,000,000 tosses | Time [sec] |
| 1 | 3.085158 | 3.872 | 3.085784 | 39.002 |
| 2 | 3.137368 | 4.600 | 3.140937 | 46.390 |
| 3 | 3.132474 | 3.440 | 3.131164 | 34.347 |
| 4 | 3.141878 | 2.031 | 3.140465 | 19.842 |
Discussion
The FormCreate method creates the in-memory bitmaps used for the
"toss" and "graph" areas. These bitmaps are only freed when the
program terminates. The FormCreate sets up the TSimplePantograph,
which is a tool for mapping from "real world" coordinates to a particular pixel
in a bitmap. FormCreate also establishes DirectionVectorRoutine
:= DirectionMethod2 (on the Setup tab) as the default method of creating
random directions (discussed more below).
The ResetState method is called on FormCreate and whenever the Reset button is pressed, or when the Setup algorithm is changed.. ResetState clears the "toss" and "graph" areas and draws the axes on the graph. The parallel lines are drawn using the value specified in the "Areas" TSpinBox. When there are N areas, there are N+1 lines, including the top and bottom lines of the "toss" area.
A ProgressBar and Cancel button appears when more than 100 needles are tossed. Pressing the Cancel button sets a flag that is recognized inside the computation loop. Application.ProcessMessages is called when each 1% of the calculations have been completed. (Actually, it's called after each 0.2% of calculations are completed when more than 1,000,000 tosses are made.) The computations can be completed much more quickly by unchecking the Graphics checkbox.
The ButtonTossClick method is called when the Toss button is pressed. This method loops for the specified number of times and calls the TossBuffonNeedle method.
The TossBuffonNeedle procedure assumes L = W = 1 to avoid needless computations. The value of D, which is the distance from a parallel line to the midpoint of the needle, is computed by:
D := Random - 0.5;
D ranges from -0.5 (below a line) to 0.5 (above a line). The random orientation angle, q, ranges from 0 to 180 degrees (p radians). q is defined by calling the DirectionVectorRoutine, which is a procedure variable that can have a value of Method1, Method2, Method3, or Method 4. These direction methods will be discussed later, but each routine defines the values x, y, Sin(q), and Cos(q). A random direction vector is from (0,0) to (x,y) and forms an angle q with the positive X axis. (See the diagrams below explaining the various random direction generation methods.)
The Cos(q) value is only computed to display the "toss." Only Sin(q) is needed to compute whether a needle crosses a line:
| // update statistics INC (TossCount); IF ABS(D) <= 0.5*SinTheta THEN INC (CrossCount); |
Once the D and q values are computed, and the statistics are updated, the "toss" is moved to a random location on the display. First the midpoint in the display space is determined randomly:
| // define midpoint of needle in throwing space xMid := Random(BitmapTosses.Width+1); // determine placement of needle midpoint yMid := PixelsBetweenParallelLines * Random(1+ParallelLineCount) + ROUND(D*PixelsBetweenParallelLines); |
With this midpoint location, the "needle" is drawn with a random color (except white):
| xDelta := ROUND(HalfLength*CosTheta); yDelta := ROUND(HalfLength*SinTheta); BitmapTosses.Canvas.Pen.Color := RGB( Random(250), Random(250), Random(250) ); BitmapTosses.Canvas.MoveTo(xMid-xDelta, yMid-yDelta); BitmapTosses.Canvas.LineTo(xMid+xDelta, yMid+yDelta) |
To update the "dot" graph, the ArcTan function is used to find the angle and the SimplePantograph is used to map the (x, y) real-world coordinates to (i, j) pixel coordinates:
| angle := ArcTan2(y,x); SimplePantograph.MapRealToPixel(angle, ABS(D), i,j); IF ABS(D) <= 0.5*SinTheta THEN BitmapGraph.Canvas.Pixels[i,j] := clRed ELSE BitmapGraph.Canvas.Pixels[i,j] := clBlack; |
Using the Pixels property (instead of Scanline) for setting the red and black dots in the graph using an in-memory bitmap wasn't that slow in this program. Once a few hundred thousand needles are "tossed," every pixel is covered and the graphical image doesn't change that much anyway.
The in-memory BitmapTosses and BitmapGraph are assigned in ButtonTossClick to update the display:
| // UpdateDisplay ImageTosses.Picture.Graphic := BitmapTosses; ImageGraph.Picture.Graphic := BitmapGraph; |
The estimate for p is updated in the UpdatePIvalue method:
| PROCEDURE TFormBuffon.UpdatePiValue; VAR PiApprox: DOUBLE; BEGIN LabelTosses.Caption := FormatFloat(',0', TossCount) + Plural(TossCount, ' Toss', ' Tosses') + ', ' + FormatFloat(',0', CrossCount) + Plural(CrossCount, ' Crossing', ''); IF CrossCount > 0 // can't divide by zero THEN BEGIN PiApprox := 2.0*TossCount/CrossCount; LabelPIApproxValue.Caption := Format('%.6f', [PiApprox]); LabelPIApprox.Visible := TRUE END ELSE BEGIN LabelPIApproxValue.Caption := ''; LabelPIApprox.Visible := FALSE END END; |
Note the Plural string function that displays the correct words: "toss" or "tosses" and "crossing" or "crossings."
Methods of Creating Random Direction Vectors
The TDirectionVectorRoutine type is defined in the ScreenBuffon unit so that a variety of methods can be explored to compute the random direction angle.
| type TDirectionVectorRoutine = PROCEDURE (VAR x, y, SinTheta, CosTheta: Single); |
The TFormBuffon class includes a private DirectionVectorRoutine variable
| private DirectionVectorRoutine : TDirectionVectorRoutine; ... |
As mentioned earlier, the TFormBuffon.FormCreate routine assigns the default DirectionMethodRoutine:
| // Setup default random direction generator DirectionVectorRoutine := DirectionMethod2; RadioButtonMethod2.Checked := TRUE; |
This assignment of a DirectionVectorRoutine is a much more elegant way in Delphi than the syntax used with function pointers in C/C++.
To force consistency the RadioButtonMethod2 RadioButton is Checked when the DirectionVectorRoutine assignment is made.
The only other way to change the DirectionMethodRoutine is by directly clicking on a RadioButton on the Setup tab.
| procedure TFormBuffon.RadioButtonMethodClick(Sender: TObject); begin CASE (Sender AS TRadioButton).Tag OF 1: DirectionVectorRoutine := DirectionMethod1; 2: DirectionVectorRoutine := DirectionMethod2; 3: DirectionVectorRoutine := DirectionMethod3; 4: DirectionVectorRoutine := DirectionMethod4; ELSE // do nothing (should never happen) END; ResetState end; |
Now that the assignment of the DirectionVectorRoutine has been discussed, let's look at each of the Random Direction Generation Methods. Remember that since we are trying to compute p that none of the algorithms can depend directly on p.
Method 1. Naive and Incorrect Approach
This was my original, naive, and INCORRECT approach to create a random angle --
it's still educational to consider.
A random (x,y) value is chosen in the rectangle ranging from -1 to 1 in the X direction and 0 to 1 in the Y direction.

The Sin and Cos of the angle, q, between the X-axis and the line from (0,0) to (x,y) were easily computed.
| // My original, naive and INCORRECT APPROACH. // Assume PI can't be used anywhere since that's what we're calculating. // Avoid use of SIN and COS. PROCEDURE DirectionMethod1 (VAR x, y, SinTheta, CosTheta: Single); VAR hypotenuse : SINGLE; BEGIN // define direction vector (x,y) to assign needle orientation x := 2.0*(Random - 0.5); // -1.0 <= x <= 1.0 y := Random; // 0.0 <= y <= 1.0 IF x = 0.0 THEN BEGIN SinTheta := 1.0; CosTheta := 0.0 END ELSE BEGIN hypotenuse := SQRT( SQR(x) + SQR(y) ); SinTheta := y / hypotenuse; CosTheta := x / hypotenuse // needed for plotting needle END END {DirectionMethod1}; |
This algorithm is simple but is not correct. Any of the (x,y) points along the line shown below will result in the same angle:

The lines from the origin (0,0) to the bounding rectangle as a function of angle are not all the same length. The resulting graph shows a higher relative probability near p/4 (45 degrees) and 3p/4 (135 degrees).

A plot of D versus q using Method 1 shows this bias:

Because of this minor bias, the estimates of p using Method 1 are usually lower than the true value.
Method 1 can be modified, as suggested by Gustavo Massaccesi from Argentina, to correct for this uneven probability distribution by angle.
Method 2. Gustavo Massaccesi's "Fix" to Method 1
My naive approach can be fixed by requiring each (x, y) point to be within the unit
semicircle.

The extra check involves using the Pythagorean formula to calculate the distance and a REPEAT .. UNTIL loop:
| // Gustavo Massaccesi's suggested improvement. Force (x,y)
value to be // inside unit semicircle within rectangle. PROCEDURE DirectionMethod2 (VAR x, y, SinTheta, CosTheta: Single); VAR hypotenuse : SINGLE; BEGIN // Generate random numbers until one is within semicircle. This // ensures all directions have an equal probability. REPEAT // define direction vector (x,y) to assign needle orientation x := 2.0*(Random - 0.5); // -1.0 <= x <= 1.0 y := Random; // 0.0 <= y <= 1.0 hypotenuse := SQRT( SQR(x) + SQR(y) ); UNTIL hypotenuse <= 1.0; IF x = 0.0 THEN BEGIN SinTheta := 1.0; CosTheta := 0.0 END ELSE BEGIN SinTheta := y / hypotenuse; CosTheta := x / hypotenuse // needed for plotting needle END END {DirectionMethod2}; |
A plot of D versus q using Method 2 shows no angular bias.
Method 3. Ingenieurbüro Dr. Elsing's
Random Arc Length Method
Ingenieurbüro Dr. Rainer Elsing from Germany suggested a different
approach to find a uniformly distributed random angle. In finding a random angle, we
pick a random arc length that is between 0 and an assumed large value that is much greater
than the circumference of the circle. Because the Sin and Cos functions are
periodic, the Sin and Cos of this random arc length can be used to calculate the
(x,y) point like that used in Methods 1 and 2.
This is the only method that directly computes Sin and Cos values for arbitrary angles.
| // Rainer Elsing's suggested improvement. ASSUME that we
don't know // the value of PI since we're trying to compute it! But allow // evaluation of SIN and COS functions. PROCEDURE DirectionMethod3 (VAR x, y, SinTheta, CosTheta: Single); CONST AnyNumber = 100; // Assumes value is >> Circumference VAR ArcLength: Single; BEGIN ArcLength := Random*AnyNumber; // Force from 3rd or 4th quadrants back to 1st or 2nd SinTheta := ABS( SIN(ArcLength) ); CosTheta := COS(ArcLength); x := CosTheta; y := SinTheta END {DirectionMethod3}; |
Method 4. Division of Semicircle into
2N Intervals Using Half-Angle Formula and Rotation Formula
An easy way to get a random direction vector, and simultaneously the Sin and Cos values,
would be to pick a point at random from a set of equally-spaced points along the
semicircle described in Method 2.
Such a set of equally-spaced points along a semicircle can be found in two steps (and without direct evaluation of Sin and Cos for arbitrary angles):
1. Recall the Half-Angle Trig Formulas:

Using these formulas, we start at 90 degrees with Cos(90 degrees) = 0 and compute the Sin and Cos of a very small angle.
As an example, let's assume we want to divide the semicircle into 23 = 8 intervals (the same logic works for any power of 2). This means we first need the Sin and Cos of 180/8 = 22.5 degrees. To compute this, we start with Cos(90 degrees) = 0, and apply the half-angle formulas twice:
| Angle [degrees] | Cos(angle) | Sin(angle) |
| 90.0 | 0.0 | not needed |
| 45.0 | 0.707107 | 0.707107 |
| 22.5 | 0.923880 | 0.382683 |
The diagram below shows the geometry of this final angle in this example:

2. Now starting at the point (1, 0) on the unit circle, rotate this point (1,0) about the origin by the angle in Step 1 using the rotation matrix.

Repeat the rotation process until the desired number of points have been computed.
Here a summary table and diagram for this 23-interval case:
| Step | x | y |
| 0 | 1.000000 | 0.000000 |
| 1 | 0.923880 | 0.382683 |
| 2 | 0.707107 | 0.707107 |
| 3 | 0.382683 | 0.923880 |
| 4 | 0.000000 | 1.000000 |
| 5 | -0.382683 | 0.923880 |
| 6 | -0.707107 | 0.707107 |
| 7 | -0.923880 | 0.382683 |
| 8 | -1.000000 | 0.000000 |

With the table of (x,y) values also being the Sin and Cos values, the DirectionMethod4 routine was quite simple:
| PROCEDURE DirectionMethod4 (VAR x, y, SinTheta, CosTheta:
Single); VAR index: INTEGER; BEGIN index := Random(LookupArrayCount+1); x := xArray[index]; y := yArray[index]; // Since x and y were defined on unit semicircle, they are also the // SinTheta and CosTheta values CosTheta := x; SinTheta := y END {DirectionMethod4}; |
The xArray and yArray are assigned values in the ScreenBuffon unit initialization. The current implementation has . LookupArrayCount = 4096. See the source code for details.
Conclusions
Buffon's method is an interesting way to compute p
using random numbers and "tossing" needles onto parallel lines.
The execution times when graphics is enabled is dominated by the graphics drawing time instead of the algorithm itself.
When graphics are disabled, the algorithms dominate the execution times. Surprisingly, the Sin and Cos functions evaluated in Method 3 were fairly quick on a Pentium. Method 4 is the fastest method since it uses a lookup table and did no other computations.
Method 4 appears to be the most accurate method, but I'm not sure how one proves accuracy in a Monte Carlo simulation.
Method 1 with its non-uniform angular probability distribution is really solving a slightly different problem than the one desired. Method 1 predictably always had a lower value for p, but was still in the right "neighborhood."
Keywords
pi, Monte Carlo method, Count Buffon, Random, Plural, TBitmap, TImage, Pixels,
MoveTo, LineTo, FillRect, TextOut, TSimplePantograph, ShellExecute,
Application.ProcessMessages, Cancel button, FormatFloat, ArcTan2, RGB,
MessageBeep(MB_ICONEXCLAMATION), Procedure variable, Half-Angle Formula, Rotation Matrix
References
Beckmann, Peter Beckmann, A History of Pi, St. Martin's Press,
1971. pp. 159-161.
Hamming, Richard W. Introduction to Applied Numerical Analysis, New York: McGraw Hill, 1971, pp. 311-312.
Ralston, Anthony Ralston (editor). Encyclopedia of Computer Science and Engineering (second edition), New York: Van Nostrand Reinhold, 1983, pp. 997-998.
Zocchi, F., "Accuracy of Buffon's 200-year-old experimental data," Nature, 1988, 336:318.
Links
Buffon's Needle: An Analysis and Simulation
www.mste.uiuc.edu/reese/buffon/buffon.html
Lazzarini Eats Humble Pi (Posthumously)
www.knowledge.co.uk/frontiers/sf096/sf096m18.htm
Acknowledgements
Thanks to Ingenieurbüro Dr. Rainer Elsing (home page in German
or English) and Gustavo Massaccesi (home
page in Spanish) for their assistance in refining random direction algorithms.
Thanks to Peter Hamer for some very useful links about Buffon's Needle.
Files
Delphi 3/4 Source (155 KB): Buffon.Zip
Updated 18 Feb 2002
since 28 Mar 1999