4

我正在尝试计算不断变化的 6 面掷骰数的几率分布。例如,3d6 的范围从 3 到 18,如下所示:

3:1, 4:3, 5:6, 6:10, 7:15, 8:21, 9:25, 10:27, 11:27, 12:25, 13:21, 14:15, 15:10, 16:6, 17:3, 18:1

我写了这个 php 程序来计算它:

function distributionCalc($numberDice,$sides=6) {
for ( $i=0; $i<pow($sides,$numberDice); $i++)
    {
    $sum=0;
    for  ($j=0; $j<$numberDice; $j++)
        { $sum+=(1+(floor($i/pow($sides,$j))) % $sides); }
    $distribution[$sum]++;
    }
return $distribution;
}

内部 $j for 循环使用底数模数函数的魔力来创建一个以 6 为基数的计数序列,其中位数是骰子的数量,因此 3d6 将被视为:

111,112,113,114,115,116,121,122,123,124,125,126,131,etc.

该函数取每个的总和,因此它会读作:3,4,5,6,7,8,4,5,6,7,8,9,5 等。它遍历所有 6^3 个可能的结果,并将 1 添加到 $distribution 数组中 3 到 18 之间的相应槽。非常简单。然而,它只工作到大约 8d6,之后我得到服务器超时,因为它现在正在进行数十亿次计算。

但我认为没有必要,因为死亡概率遵循甜美的钟形曲线分布。我想知道是否有办法跳过数字运算并直接进入曲线本身。有没有办法做到这一点,例如,80d6(范围:80-480)?可以在不进行 6^80 计算的情况下预测分布吗?

我不是专业的编码员,概率对我来说还是个新手,所以感谢所有帮助!

斯蒂芬

4

4 回答 4

3

在 PERL 中:

#!
my( $DieType, $NumDice, $Loaded ) = @ARGV;

my $subname = "D" . $DieType . ( ( $Loaded eq "Loaded" ) ? "Loaded" : "Normal" );
my $Prob = \&$subname;

my $width = 12;
my $precision = $width - 2;

printf "%5s  %-${width}s \n", "Pip:", "Frequency:"; 
for ( my $j = $NumDice; $j <= $DieType * $NumDice ; $j++ ) {
  printf "%5d  %${width}.${precision}f \n", $j, Frequency( $DieType, $NumDice, $j );
}

sub D6Normal {
  my $retval = 1/6;
}

sub D6Loaded {
  my $retval = 1/6;

  CASE: for ($_[0]) {
    /1/    && do { $retval -= 0.02/6;   last CASE; }; 
    /2..5/ && do { $retval += 0.0025/6; last CASE; }; 
    /6/    && do { $retval += 0.01/6;   last CASE; }; 
  }
  return $retval;
}

sub D8Normal {
  my $retval = 1/8;
}

sub D10Normal {
  my $retval = 1/10;
}

sub D10Loaded {
  my $retval = 1/10;

  CASE: for ($_[0]) {
    /1..8/ && do { last CASE; }; 
    /9/    && do { $retval -= 0.01/10;  last CASE; }; 
    /10/   && do { $retval += 0.01/10;  last CASE; }; 
  }
  return $retval;
}

sub D12Normal {
  my $retval = 1/12;
}

sub D20Normal {
  my $retval = 1/20;
}

sub D32Normal {
  my $retval = 1/32;
}

sub D100Normal {
  my $retval = 1/100;
}

sub Frequency {
  my( $DieType, $NumberofDice, $PipCount ) = @_;

  if ( ( $PipCount > ($DieType * $NumberofDice) ) || ( $PipCount < $NumberofDice ) ) { 
    return 0; 
  }

  if ( ! exists $Freq{$NumberofDice}{$PipCount} ) {
    if ( $NumberofDice > 1 ) {
      for ( my $i = max( 1, $PipCount - $DieType ); $i <= min( $DieType * ($NumberofDice - 1), $PipCount - 1 ); $i++ ) {
        $Freq{$NumberofDice}{$PipCount} += &$Prob( $PipCount - $i ) * Frequency( $DieType, $NumberofDice - 1, $i );
      }
    } else {
      $Freq{$NumberofDice}{$PipCount} = &$Prob( $PipCount );
    }
  }
  return $Freq{$NumberofDice}{$PipCount}; 
}

sub max {
  my $max = shift(@_);
  foreach my $arg (@_) {
    $max = $arg if $max < $arg;
  }
  return $max;
}

sub min {
  my $min = shift(@_);
  foreach my $arg (@_) {
    $min = $arg if $min > $arg;
  }
  return $min;
}
于 2012-12-13T21:34:26.010 回答
1

您正在寻找二项分布

于 2010-04-21T16:41:30.127 回答
1

好的,让我们从只掷一个骰子开始。我们知道平均值是 3.5。我们还可以计算方差,

sum(p(x) * (x - M)^2),其中 M 是平均值,x 是骰子结果,p 是该骰子结果的概率。

使用这个公式,单次掷骰子的方差为 35/12 = 1/6*((-2.5)^2 + (-1.5)^2 + (-0.5)^2 + 0.5^2 + 1.5^2 + 2.5^2)

这也是一个事实,对于来自同一分布的多个独立样本,它们的方差会增加。所以,如果你掷 N 个骰子,你应该得到一个新的分布,平均值为 3.5*N,方差为 35*N/12。

因此,如果您生成平均值为 3.5*N 且方差为 35*N/12 的正态分布,假设您掷出的骰子数量相当可观,那么它将非常适合。

于 2010-04-21T16:50:36.680 回答
0

我想知道是否有办法跳过数字运算并直接进入曲线本身。有没有办法做到这一点,例如,80d6(范围:80-480)?可以在不进行 6^80 计算的情况下预测分布吗?

是的。自变量和的概率函数是每个变量的概率函数的卷积。

这种情况下的卷积只是一个特殊的求和。(更一般地说,卷积是一个积分。)设 p 和 q 是两个离散的概率函数。卷积通常用星号表示。

(p * q)[i] = sum_{j=1}^(n_p) p[j] q[i - j + 1]

其中 i 的范围从 1 到 (n_p + n_q - 1),其中 n_p 是 p 的元素数,n_q 是 q 的元素数。如果 (i - j + 1) 小于 1 或大于 n_q,则令 q[i - j + 1] 为零(因此这些项从求和中消失)。

在手头的情况下,您有 p = q = [1/6, 1/6, 1/6, 1/6, 1/6, 1/6], n_p = n_q = 6。 3 卷是 (p * p * p)。80 卷总和的分布是 (p * p * p * ... (76 more p's) ... * p)。

我不懂 PHP,所以我用Maxima写了一个小程序。

discrete_conv (p, q) := makelist (discrete_conv1 (p, q, i), i, 1, length (p) + length (q) - 1);
discrete_conv1 (p, q, i) := sum (p [j] * foo (q, i - j + 1), j, 1, length (p));
foo (a, i) := if 1 <= i and i <= length (a) then a [i] else 0;
r : [1/6, 1/6, 1/6, 1/6, 1/6, 1/6];
discrete_conv (r, discrete_conv (r, r));
 => [1/216,1/72,1/36,5/108,5/72,7/72,25/216,1/8,1/8,25/216,7/72,
     5/72,5/108,1/36,1/72,1/216]

如果你继续重复discrete_conv,你会发现数字变得越来越像一个正态分布。这是中心极限定理的说明。

我完全有可能在索引方面犯了一些错误,所以你需要检查一下。希望这能对问题有所了解。

于 2012-12-21T16:35:19.930 回答