趣味 SQL:使用蒙特卡洛方法计算圆周率 Pi 的值

作者: 不剪发的Tony老师
毕业于北京航空航天大学,十多年数据库管理与开发经验,目前在一家全球性的金融公司从事数据库架构设计。CSDN学院签约讲师以及GitChat专栏作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net


圆周率(Pi)是圆的周长与直径的比值,一般用希腊字母 π 表示,是一个在数学及物理学中普遍存在的数学常数。π 也等于圆形之面积与半径平方之比,是精确计算圆周长、圆面积、球体积等几何形状的关键值。

蒙特卡洛方法(Monte Carlo method)也称统计模拟法、统计试验法。蒙特卡洛方法的基本思想是:首先建立一个描述问题的概率模型或随机过程,使它的参数或数字特征等于问题的解;然后通过对模型或过程的观察或抽样试验来计算这些参数或数字特征;最后给出所求解的近似值。

使用蒙特卡洛法求解 π 值基本步骤如下:构造一个单位正方形和一个单位圆的 1/4;往正方形内随机投点,根据点到原点的距离是否大于 1 判断点是落在 1/4 的圆内还是在圆外。




















根据落在两个不同区域的点的数目,例如总的点数为 N,1/4 圆内的点数为 n。如果次数够多,根据两者的面积公式:

n N ≈ 1 4 π 1. 0 2 1. 0 2 = π 4 \frac n N \approx \frac {{\frac1 4}{\pi 1.0^2}} {1.0^2} = \frac \pi 4 Nn​≈1.0241​π1.02​=4π​

也就是说,

π ≈ 4 n N \pi \approx 4 \frac n N π≈4Nn​

我们只要知道 n 和 N 的值,就可以获得 π 的近似值。

接下来我们介绍如何在不同的数据库中实现以上过程,首先是 MySQL:

with recursive pi(x, y, n) as (
select rand() x,rand() y, 1 n
union all
select rand(), rand(), n+1 from pi where n<100
)
select 4sum(1-floor(xx+yy))/count() val from pi;

val
3.16

其中,WITH 表示通用表表达式。关于通用表表达式(Common Table Expression)的详细介绍可以参考这篇文章。

rand() 函数随机返回 0 到 1 之间的数字;floor() 函数用于返回小于等于参数的整数,这里表示点落在 1/4 圆内(0)或者圆外(1)。

在这里插入图片描述
随着次数的增加,模拟计算出的值越接近 π 的真实值。

其他的数据库也可以使用类似的方式进行模拟,语法略微有些差异:

– Oracle 使用 dbms_random.value() 函数获取随机值
with pi(x, y, n) as (
select dbms_random.value() x,dbms_random.value() y, 1 n from dual
union all
select dbms_random.value(), dbms_random.value(), n+1 from pi where n<10000
)
select 4sum(1-floor(xx+yy))/count() val from pi;

VAL
3.166

– SQL Server rand() 函数在一个 SELECT 中只返回一个相同的值,需要设置不同的 seed
with pi(x, y, n) as (
select rand(CHECKSUM(NEWID())) x,rand(CHECKSUM(NEWID())) y, 1 n
union all
select rand(CHECKSUM(NEWID())), rand(CHECKSUM(NEWID())), n+1 from pi where n<10000
)
select 4sum(1-floor(xx+yy))/count() val from pi OPTION( MAXRECURSION 32767);

val
3.134

– PostgreSQL 使用 random() 函数获取随机数
with recursive pi(x, y, n) as (
select random() x,random() y, 1 n
union all
select random(), random(), n+1 from pi where n<10000
)
select 4sum(1-floor(xx+yy))/count() val from pi;

val
3.1476

– SQLite random() 函数返回 -9223372036854775808 到 +9223372036854775807 之间的数字
with recursive pi(x, y, n) as (
select abs((random()/9223372036854775808.0)) x,abs((random()/9223372036854775808.0)) y, 1 n
union all
select abs((random()/9223372036854775808.0)), abs((random()/9223372036854775808.0)), n+1 from pi where n<10000
)
select 4sum(1.0-floor(xx+yy))/count() val from pi;

val
3.1584

除了 Oracle 之外,以上其他数据库都提供了 pi() 函数。