趣味 SQL:使用蒙特卡洛方法計算圓周率 Pi 的值
作者: 不剪發(fā)的Tony老師
畢業(yè)于北京航空航天大學(xué),十多年數(shù)據(jù)庫管理與開發(fā)經(jīng)驗,目前在一家全球性的金融公司從事數(shù)據(jù)庫架構(gòu)設(shè)計。CSDN學(xué)院簽約講師以及GitChat專欄作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net
圓周率(Pi)是圓的周長與直徑的比值,一般用希臘字母 π 表示,是一個在數(shù)學(xué)及物理學(xué)中普遍存在的數(shù)學(xué)常數(shù)。π 也等于圓形之面積與半徑平方之比,是精確計算圓周長、圓面積、球體積等幾何形狀的關(guān)鍵值。
蒙特卡洛方法(Monte Carlo method)也稱統(tǒng)計模擬法、統(tǒng)計試驗法。蒙特卡洛方法的基本思想是:首先建立一個描述問題的概率模型或隨機過程,使它的參數(shù)或數(shù)字特征等于問題的解;然后通過對模型或過程的觀察或抽樣試驗來計算這些參數(shù)或數(shù)字特征;最后給出所求解的近似值。
使用蒙特卡洛法求解 π 值基本步驟如下:構(gòu)造一個單位正方形和一個單位圓的 1/4;往正方形內(nèi)隨機投點,根據(jù)點到原點的距離是否大于 1 判斷點是落在 1/4 的圓內(nèi)還是在圓外。
根據(jù)落在兩個不同區(qū)域的點的數(shù)目,例如總的點數(shù)為 N,1/4 圓內(nèi)的點數(shù)為 n。如果次數(shù)夠多,根據(jù)兩者的面積公式:
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 的值,就可以獲得 π 的近似值。
接下來我們介紹如何在不同的數(shù)據(jù)庫中實現(xià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 表示通用表表達式。關(guān)于通用表表達式(Common Table Expression)的詳細介紹可以參考這篇文章。
rand() 函數(shù)隨機返回 0 到 1 之間的數(shù)字;floor() 函數(shù)用于返回小于等于參數(shù)的整數(shù),這里表示點落在 1/4 圓內(nèi)(0)或者圓外(1)。
隨著次數(shù)的增加,模擬計算出的值越接近 π 的真實值。
其他的數(shù)據(jù)庫也可以使用類似的方式進行模擬,語法略微有些差異:
– Oracle 使用 dbms_random.value() 函數(shù)獲取隨機值
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() 函數(shù)在一個 SELECT 中只返回一個相同的值,需要設(shè)置不同的 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() 函數(shù)獲取隨機數(shù)
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() 函數(shù)返回 -9223372036854775808 到 +9223372036854775807 之間的數(shù)字
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 之外,以上其他數(shù)據(jù)庫都提供了 pi() 函數(shù)。