在一次采访中,我最初使用笔/纸解决了以下问题,然后通过程序验证结果。
问题如下:
有三个人A,B和C.每个人都能够分别以6 / 7,4 / 5和3/4的概率击中目标。如果他们每次射击一次射击的概率是多少,那么他们中的两个将会击中目标?
答案是:
P(...) = P(A)*P(B)*(1-P(C)) +
P(B)*P(C)*(1-P(A)) +
P(C)*P(A)*(1-P(B))
= 27.0/70.0
= 38.57142857142857142857142857142857142857....%
以下是我解决问题的方法:
#include <cstdio>
#include <cctype>
#include <ctime>
#include <random>
int main()
{
std::mt19937 engine(time(0));
engine.discard(10000000);
std::uniform_real_distribution<double> uniform_real(0.0,1.0);
double prA = (6.0 / 7.0);
double prB = (4.0 / 5.0);
double prC = (3.0 / 4.0);
std::size_t trails = 4000000000;
std::size_t total_success = 0;
for (std::size_t i = 0; i < trails; ++i)
{
int current_success = 0;
if (uniform_real(engine) < prA) ++current_success;
if (uniform_real(engine) < prB) ++current_success;
if (uniform_real(engine) < prC) ++current_success;
if (current_success == 2)
++total_success;
double prob = (total_success * 1.0) / (i+1);
if ((i % 1000000) == 0)
{
printf("%05d Pr(...) = %12.10f error:%15.13f\n",
i,
prob,
std::abs((27.0/70.0) - prob));
}
}
return 0;
}
问题如下,无论我运行多少试验,概率平线大约在0.3857002101左右。代码中有什么问题吗?
采访者表示,无论种子如何,在100万次试验中将结果收敛到小数点后9位精确度是微不足道的。
关于错误在我的代码中的位置的任何想法?
更新1: 我已经尝试了上面的代码与以下生成器,他们似乎platau大约在同一时间大致试验10 ^ 9。
更新2: 考虑到这个问题,我走了下面的轨道。比率27/70由27和70组成,它们都是互质的,并且在4×10 ^ 9下的因子为约57×10 ^ 6或所有数字的约1.4%。因此,在[0,4x10 ^ 9]之间随机选择的两个数字中获得27/70的“精确”比率的概率大约是1.4%(因为在4x10 ^ 9内有更多因素为27) - 所以得到确切的比例非常低,无论试验次数如何,这个数字都是恒定的。
现在,如果要谈论厚边界 - 即:70 + / 5因子范围内的数字,这会增加在[0,4x10 ^ 9]范围内随机选择一对数字的概率将指定/相关容差内的比率约为14%左右,但是使用这种技术,与精确值相比,我们可以获得的最佳平均值大约为5位小数。这种推理方式是否正确?
答案 0 :(得分:8)
采访者表示,无论种子如何,在100万次试验中将结果收敛到小数点后9位精确度是微不足道的。
嗯,这显然是荒谬的。一百万次试验中,你无法得到千分之一的估计值。如果总数只有一个不同于理论值,那么你将减去一百万分之一,这比“小数点后9位”大一千倍。
顺便说一句,c ++ 11带有一个非常好的uniform_int_distribution函数,它实际上正确地处理了舍入:它将统一生成器的总范围分成所需范围的精确倍数和余数,并丢弃在余数中生成的值,因此生成的值不会被舍入所偏差。我对你的测试程序进行了一些修改,它在十亿次试验中确实收敛到六位数,这与我的预期相符:
int main() {
std::mt19937 engine(time(0));
std::uniform_int_distribution<int> a_distr(0,6);
std::uniform_int_distribution<int> b_distr(0,4);
std::uniform_int_distribution<int> c_distr(0,3);
std::size_t trials = 4000000000;
std::size_t total_success = 0;
for (std::size_t i = 1; i <= trials; ++i) {
int current_success = 0;
if (a_distr(engine)) ++current_success;
if (b_distr(engine)) ++current_success;
if (c_distr(engine)) ++current_success;
if (current_success == 2) ++total_success;
if ((i % 1000000) == 0) {
printf("%05d Pr(...) = %12.10f error:%15.13f\n",
i,
double(total_success) / i,
std::abs((27.0/70.0) - double(total_success) / i));
}
}
}
返回0;
答案 1 :(得分:8)
首先,一些基本的数学表明,只有一百万次试验才能获得9个精度。鉴于我们的概率为27/70
,我们可以计算出x/1000000 = 27/70
x = 385714.28571
。如果我们有一个非常非常精确的均匀随机数发生器,它可以准确地生成385714个正确的试验,这将给我们一个大约abs(385714/1000000 - 0.38571428571428573) = 2.857142857304318e-07
的误差,这个误差远远超出了所需的9个精度位置。
我不认为你的分析是正确的。给定非常准确的分布,当然可以获得所需的精度。然而, - 分布均匀性的偏斜将严重妨碍精度。如果我们进行10亿次试验,我们所希望的最佳精度是2.85 * 10^-10
左右。如果分布偏差甚至为100,那么这将被推至约1 * 10^-7
。我不确定大多数PRNG发行版的准确性,但问题是如何准确到这个程度。快速使用std::uniform_real_distribution<double>(0.0, 1.0)
,看起来肯定会有更多的差异。
答案 2 :(得分:7)
蒙特卡罗方法倾向于缓慢收敛 - 在n次模拟之后您期望的误差与1 / sqrt(n)成比例。实际上,10 ^ 9次试验后的五位精确度似乎是正确的。这里没有数字伏都教。
如果面试官正在谈论直接蒙特卡罗的抽样方法,那么......经过一百万次试验后,他可以获得九位数的准确性是不可信的。
答案 3 :(得分:3)
因为概率是以有理数给出的(在分母中有小整数),你可以将可能的情况视为维度为7x5x4的立方体(这使得140(分母的乘积)子立方体)。您可以按如下方式显式访问每个子多维数据集,而不是随机跳转,并获得140次迭代中的确切数字:
#include <cstdio>
#include <cctype>
#include <ctime>
#include <random>
int main()
{
std::size_t total_success = 0, num_trials = 0;
for (unsigned a = 1; a <= 7; ++a)
{
unsigned success_a = 0;
if (a <= 6)
// a hits 6 out of 7 times
success_a = 1;
for (unsigned b = 1; b <= 5; ++b)
{
unsigned success_b = 0;
if (b <= 4)
// b hits 4 out of 5 times
success_b = 1;
for (unsigned c = 1; c <= 4; ++c)
{
unsigned success_c = 0;
// c hits 3 out of 4 times
if (c <= 3)
success_c = 1;
// count cases where exactly two of them hit
if (success_a + success_b + success_c == 2)
++total_success;
++num_trials;
} // loop over c
} // loop over b
} // loop over a
double prob = (total_success * 1.0) / num_trials;
printf("Pr(...) = %12.10f error:%15.13f\n",
prob,
std::abs((27.0/70.0) - prob));
return 0;
}
答案 4 :(得分:1)
FWIW以下Java似乎正在收集上面的预测答案,大概是你预期的速度(它计算最坏情况误差的标准偏差)
import java.util.Random;
import java.security.SecureRandom;
/** from question in Stack Overflow */
public class SoProb
{
public static void main(String[] s)
{
long seed = 42;
/*
In an interview, I was given the following problem to solve initially using pen/paper, then via a program to verify the result.
The question is as follows:
There are three people A,B and C. Each person is capable of hitting a target with a probability of 6/7, 4/5 and 3/4 respectively. What is the probability that if they were to each fire one shot that exactly two of them will hit the target?
The answer is:
P(...) = P(A)*P(B)*(1-P(C)) +
P(B)*P(C)*(1-P(A)) +
P(C)*P(A)*(1-P(B))
= 27.0/70.0
= 38.57142857142857142857142857142857142857....%
Below is my solution to the problem:
*/
/*
int main()
{
std::mt19937 engine(time(0));
*/
Random r = new Random(seed);
// Random r = new SecureRandom(new byte[] {(byte)seed});
// std::uniform_real_distribution<double> uniform_real(0.0,1.0);
double prA = (6.0 / 7.0);
double prB = (4.0 / 5.0);
double prC = (3.0 / 4.0);
// double prB = (6.0 / 7.0);
// double prC = (4.0 / 5.0);
// double prA = (3.0 / 4.0);
double pp = prA*prB*(1-prC) +
prB*prC*(1-prA) +
prC*prA*(1-prB);
System.out.println("Pp " + pp);
System.out.println("2870 " + (27.0 / 70.0));
// std::size_t trails = 4000000000;
int trails = Integer.MAX_VALUE;
// std::size_t total_success = 0;
int total_success = 0;
int aCount = 0;
int bCount = 0;
int cCount = 0;
int pat3 = 0; // A, B
int pat5 = 0; // A, C
int pat6 = 0; // B, C
double pat3Prob = prA * prB * (1.0 - prC);
double pat5Prob = prA * prC * (1.0 - prB);
double pat6Prob = prC * prB * (1.0 - prA);
System.out.println("Total pats " +
(pat3Prob + pat5Prob + pat6Prob));
for (int i = 0; i < trails; ++i)
{
int current_success = 0;
// if (uniform_real(engine) < prA) ++current_success;
int pat = 0;
if (r.nextDouble() < prA)
{
++current_success;
aCount++;
pat += 1;
}
// if (uniform_real(engine) < prB) ++current_success;
if (r.nextDouble() < prB)
{
++current_success;
bCount++;
pat += 2;
}
// if (uniform_real(engine) < prC) ++current_success;
if (r.nextDouble() < prC)
{
++current_success;
cCount++;
pat += 4;
}
switch (pat)
{
case 3:
pat3++;
break;
case 5:
pat5++;
break;
case 6:
pat6++;
break;
}
if (current_success == 2)
++total_success;
double prob = (total_success + 1.0) / (i+2);
if ((i % 1000000) == 0)
{
/*
printf("%05d Pr(...) = %12.10f error:%15.13f\n",
i,
prob,
std::abs((27.0/70.0) - prob));
*/
System.out.println(i + "P rob = " + prob +
" error " + Math.abs((27.0 / 70.0) - prob));
Double maxVar = 0.25 / i;
System.out.println("Max stddev " + Math.sqrt(maxVar));
double ap = (aCount + 1.0) / (i + 2.0);
double bp = (bCount + 1.0) / (i + 2.0);
double cp = (cCount + 1.0) / (i + 2.0);
System.out.println("A error " + (ap - prA));
System.out.println("B error " + (bp - prB));
System.out.println("C error " + (cp - prC));
double p3Prob = (pat3 + 1.0) / (i + 2.0);
double p5Prob = (pat5 + 1.0) / (i + 2.0);
double p6Prob = (pat6 + 1.0) / (i + 2.0);
System.out.println("P3 error " + (p3Prob - pat3Prob));
System.out.println("P5 error " + (p5Prob - pat5Prob));
System.out.println("P6 error " + (p6Prob - pat6Prob));
System.out.println("Pats " + (pat3 + pat5 + pat6) +
" success " + total_success);
}
}
}
}
当前输出:
1099000000P rob = 0.3857148864682168错误6.00753931045972E-7
Max stddev 1.508242443516904E-5
错误-2.2208501193610175E-6
B错误1.4871155568862982E-5
C错误1.0978161945063292E-6
P3错误-1.4134927830977695E-7
P5错误-5.363291293969397E-6
P6错误6.1072143395513034E-6
Pats 423900660成功423900660