在 PHP 中使用圆形 - 方形飞镖靶模拟来近似计算圆周率

用圆和正方形近似计算圆周率

注意:这篇文章发布已超过两年,因此其中包含的信息可能已过时。如果您发现了问题,请留下评论,我公司会尽力更正。

2024年5月26日 - 阅读时长10分钟

几周前是圆周率日,我最近偶然想到了这种简单的圆周率近似计算方法。由于它看起来挺简单的,我决定用PHP编写一个示例。

这种圆周率近似计算方法围绕一个现实世界的例子展开,我们可以用代码来模拟它。

假设你有一个飞镖靶,它刚好能放在一个正方形区域内,与正方形的边缘齐平。然后找一个特别不擅长飞镖的人,让他开始投掷飞镖。记录飞镖击中的位置,在投掷大约一百万次后,你会得到击中飞镖靶的飞镖数量和击中背景的飞镖数量。

利用这些数字,你可以使用以下公式大致计算出圆周率的近似值。

(击中飞镖靶的次数 / (击中背景的次数 + 击中飞镖靶的次数)) * 4

这里飞镖靶的面积是πr2,背景板的面积是4r2。因此,飞镖击中圆的概率是π/4(即πr2/4r2),所以我们可以通过乘以4得到圆周率的原始值。

这是蒙特卡罗模拟的一个例子,蒙特卡罗模拟是一种预测不确定事件可能结果的技术。

本质上,有了足够的数据点,你就可以描绘出可能的结果,并大致了解如果你再进行一次操作会得到什么样的结果。我们在这里预测的是飞镖击中飞镖靶外区域的可能性,这个值接近圆周率。

为了在计算机上模拟这个例子,我们首先需要创建游戏区域,它是一个正方形图像,正中心有一个圆。以下PHP代码可以实现这一点,使用imagefilledrectangle()和imagefilledellipse()函数以不同颜色绘制我们感兴趣的两个图形。

<?php

$height = 500;
$width = 500;

// 创建图像。
$img = imagecreatetruecolor($width, $height);

// 设置颜色。
$red = imagecolorallocate($img, 255, 0, 0);
$green = imagecolorallocate($img, 0, 255, 0);

// 绘制背景矩形。
imagefilledrectangle($img, 0, 0, $width, $height, $red);
// 绘制前景圆。
imagefilledellipse($img, $height / 2, $width /2, $width, $height, $green);

// 生成图像。
imagepng($img, 'pi' . '.png');

// 销毁图像资源。
imagedestroy($img);

现在我们要做的是遍历图像上的大量随机点,使用imagecolorat()函数查看这些点的颜色。如果颜色是红色,那么说明击中了背景,否则我们就认为击中了圆,并记录击中圆的次数。这种方法避免了使用任何实际的数学运算来判断点是在圆内还是圆外,但这当然是可行的。

我们在这里使用替代随机函数mt_rand(),是为了尽量减轻PHP原生rand()函数的周期性问题。

// 设置变量来统计随机点。
$circle = 0;
$background = 0;

// 循环x次并记录落点。
for ($i = 0; $i < 10_000_000_000; $i++) {
  $colour = imagecolorat($img, mt_rand(0, $width - 1), mt_rand(0, $height - 1));
  if ($colour === $red) {
    // 记录击中背景的次数。
    $background++;
  } else {
    // 记录击中圆的次数。
    $circle++;
  }
}

利用这个循环的结果,我们可以输出结果并进行最终计算,如下所示。

// 报告结果。
echo '击中圆的次数: ' . $circle . PHP_EOL;
echo '击中背景的次数: ' . $background . PHP_EOL;
echo '比率: ' . (($circle/($background+$circle))*4) . PHP_EOL;

使用100亿(10,000,000,000)个点,运行这个程序需要几分钟时间,并产生以下输出。

击中圆的次数: 7868623953
击中背景的次数: 2131376047
比率: 3.1474495812

嗯,这个结果很接近,但并不是一个很好的近似值。我公司当然不会用这种技术来计算圆周率的实际值,因为这需要很长时间,而且得出的结果也不太有用。

当然,对于这种近似计算方法,图像越大,点数越多,近似效果就越好。在我公司的计算机上,使用100亿个点时,脚本运行了30多分钟才完成,所以设置更大的值会花费更长时间,而且也不一定能得到理想的结果。

如果我公司只是将原始图像的大小改为10000 x 10000,然后使用1000万个点进行模拟,得出的圆周率是3.1417928。这个值更接近圆周率,但这更多是模拟随机性的结果。

圆周率出现在各种不寻常的地方,不过这里并不一定算特别不寻常,因为它确实与圆有关。在Drupal开发Drupal模块开发以及Drupal升级等工作中,偶尔也会涉及到一些数学计算,说不定也会用到圆周率相关的知识。尤其是Drupal11,随着其不断发展,可能会在更多场景中遇到各类计算。你最喜欢的圆周率近似计算方法是什么呢?请在下面的评论中告诉我。