利用蒲丰投针,对π进行区间估计。
背景
这几天看到科学界利用引力波对$\pi$进行区间估计的新闻,联想到概率论当中所学的蒲丰投针实验也是利用蒙特卡洛方法对$\pi$进行估计,不过是点估计,因此想试试能否进行区间估计。
公式
蒲丰投针实验中,针与线相交的概率为
其中$l$为针的长度,$a$为平行线的间距。
反复进行投针的实验,每一次的结果均为独立的,且取值为两个离散的值:相交或者不相交。因此,多次投针的结果分布为二项分布,二项分布的总体率$P$的$1-\alpha$的置信区间为:
其中,$p$为样本率,$u_{\alpha/2}$为正态分布的$\alpha/2$分位数。根据此即可计算$\pi$的置信区间。
编程的实现
蒲丰投针的实验
这个比较简单,不过一个点在于生成高质量的随机数,C++11的标准库提供了非常好的方法:
unsigned seed1 = std::chrono::system_clock::now().time_since_epoch().count();
std::uniform_real_distribution<double> dis(-1.0, 1.0);
std::default_random_engine rd;
std::mt19937 gen(seed1);
在C++ random库中可自定义分布类型、随机数引擎、随机数算法(mt19937)以及初始种子等,配置灵活,功能强大。
C++中正态分布分位数的计算
在C++标准库中能够生成各种分布的随机数,但是没有内置的函数用于计算各种分布的概率密度函数(PDF)、分布函数(CDF)、分位数(quantile)等。不过还好也不用自己去实现,因为boost的math库提供了各种常用分布的这些函数计算。其中,对于正态分布的分布函数计算使用erfc
函数计算,而分位数则使用erfc_inv
函数计算,具体可以参阅特殊函数的相关教材书籍。
const double confidenceInterval = 0.95;
boost::math::normal_distribution<double> my_dist;
double probability = static_cast<double>(in) / static_cast<double>(N) ;
double u = boost::math::quantile(my_dist, 1.0 - (1.0 - confidenceInterval) / 2.0);
double delta = u * std::sqrt(probability * (1.0 - probability) / N);
double p_min = probability - delta;
double p_max = probability + delta;
实验结果
取95%的置信区间,投针多次得到$\pi$的估计值如下:
投针次数 | $\hat\pi_{min}$ | $\hat\pi$ | $\hat\pi_{max}$ |
---|---|---|---|
1000 | 2.96319 | 3.068 | 3.17281 |
10000 | 3.11144 | 3.1436 | 3.17576 |
100000 | 3.13279 | 3.14296 | 3.15313 |
1000000 | 3.1381 | 3.14132 | 3.14454 |