使用 threadlocalrandom 模拟掷骰子不会收敛到预期值

问题描述

我试图验证 Java 中的 expected value formula
我做了一些非常简单的事情,我模拟了掷骰子,但我将每一面映射到一个特定的值。所以每个值都有 1/6 的概率。
预期值确定为:232.65
我运行了很长时间的骰子模拟,准确地说是10000000,但数字没有收敛到232.65而是232.74。较小的运行也波动到值 233.97。跑到2100000000给了232.63
所以我想知道我是否做错了什么。我在这里的主要前提是,我不需要模拟 20 亿次掷骰子来最终通过公式计算出预期值。
所以我想知道我是否使用了错误的随机 API。我无法获得种子,因此我无法验证每次迭代是否会发生变化。我的代码如下:

Map<Integer,Float> map = new HashMap<>();
map.put(1,17.2f);
map.put(2,11f);
map.put(3,128f);
map.put(4,1f);
map.put(5,1200f);
map.put(6,38.7f);

double sum = 0;
int count = 0;

for(Map.Entry<Integer,Float> entry: map.entrySet()) {
     sum += (entry.getValue() * (1/6f));
}

System.out.println("EV " + sum);
sum = 0;

for(int j = 0; j < 2100000000; j++) {
    int dice = ThreadLocalRandom.current().nextInt(1,7);
    sum += map.get(dice);
    ++count;
}
System.out.println(sum / count);

输出:

EV 232.65000109374523
232.63201358102154

我预计在更早的运行次数之后我会一直得到 ~232.650...

评论后更新 尝试使用 BigDecimal 但没有区别:

//double sum = 0;
BigDecimal sum = BigDecimal.valueOf(0);
int count = 0;

for(Map.Entry<Integer,Float> entry: map.entrySet()) {
    sum = sum.add(BigDecimal.valueOf(entry.getValue() * (1/6f)));
}

System.out.println("EV " + sum);

sum = BigDecimal.valueOf(0);

for(int j = 0; j < 10000000; j++) {
    int dice = ThreadLocalRandom.current().nextInt(1,7);
    sum = sum.add(BigDecimal.valueOf(map.get(dice)));
    ++count;
}

System.out.println(sum.divide(BigDecimal.valueOf(count)));

输出:

EV 232.6500010937452306
232.3528356843933100044325

解决方法

这正是您所期望的,有关详细信息,请参阅维基百科上的 standard error of the mean

就您而言,您的平均值为 232.65,标准差为 ~434.65。要了解预期方差,您可以这样做:

lower = mu - 2 * sd / sqrt(n)
upper = mu + 2 * sd / sqrt(n)

对于 n=2100000000,这给出了大约 [232.631,232.669],与您和 m0skit0 观察到的值一致。通常,您应该期望在大约 1/20 的时间内看到超出此范围的值,即这大致是 95% CI。如果您不这样做,则 RNG 不会随机统一选择替代方案。

您遇到的“缓慢”收敛是由于 sqrt(n),即需要 100 倍的工作才能为您提供另一个十进制数的精度。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...