我写了以下函数
bool random_bool(double probability)
{
double p_scaled = probability * (RAND_MAX+1) - rand();
if ( p_scaled >= 1 ) return true;
if ( p_scaled <= 0 ) return false;
return random_bool( p_scaled );
}
鉴于,rand()
在{0,1,...,RAND_MAX-1,RAND_MAX}
上从均匀分布生成一个数字,并且后续调用中的数字可以被视为独立于除密码术之外的所有实际目的,这应该以概率返回true
p
:两个if
语句返回true
,概率略低于p
,false
概率略高于1-p
,而递归调用处理其他一切。
然而,以下测试失败:
long long N = 10000000000; //1e10
double p = 10000.0 / N;
int counter = 0;
for (long long i=0;i<N;i++) if (random_bool(p)) counter++;
assert(9672 < counter && counter <= 10330);
断言语句仅在0.1%的情况下失败。但是它始终失败(counter
介于10600和10700之间)。
怎么了?
P.S。:我看过this问题,但没有帮助...
答案 0 :(得分:2)
随机数发生器中的一个常见缺陷是对较小结果的轻微偏差(基本上是高阶位向0的轻微偏差)。当将RNG内部状态包装到输出范围时,通常会发生这种情况,使用一个简单的mod,除非RAND_MAX是内部状态大小的除数,否则它会偏向高值。这是一个典型的有偏差的映射实现:
static unsigned int state;
int rand() {
state = nextState(); /* this actually moves the state from one random value to the next, eg., using a LCG */
return state % RAND_MAX; /* biased */
}
出现偏差是因为较低的值输出a在状态下具有一个mod下的映射。例如,如果状态可以具有值0-9(10个值),并且RAND_MAX是3(因此值0-2),那么% 3
操作将导致,具体取决于状态
Output State
0 0 3 6 9
1 1 4 7
2 2 5 8
结果0被过度表示,因为它有4/10的机会被选中,而其他数值则为3/10。
作为具有更可能值的示例,如果内部RNG状态是16整数,并且RAND_MAX
是35767(正如您在平台上提到的那样),那么所有值[0,6000]将输出3个不同的状态值,但剩余的~30,000个值仅输出2个不同的状态值 - 显着的偏差。这种偏见会导致您的计数器值高于预期(因为小于rand()的均匀回报有利于p_scaled >= 1
条件。
如果您可以在平台上发布rand()的确切实现,这将有所帮助。如果结果是高位偏差,你可以通过将你从rand()获得的值传递给一个好的哈希函数来消除这种情况,但更好的方法可能就是使用高质量的随机源数字,例如Mersenne Twister 。更好的发生器也将具有更大的输出范围(有效,更高的RAND_MAX),这意味着您的算法将遭受更少的重试/更少的递归。
即使Visual Studio运行时实现遭受此缺陷,值得注意的是,它可能至少部分是有意的设计选择 - 使用像状态大小(通常为2的幂)的RAND_MAX,如35767 ),确保较低位的随机性较好,因为%运算有效地混合了高位和低位 - 并且具有偏置/非随机低位的位置在实践中通常比高位位中的轻微偏置更大。 rand()
调用者无处不在的使用%来减少范围,这有效地仅使用低阶位作为2的幂(也非常常见)的模数。
答案 1 :(得分:1)
我在Linux中尝试了你的代码,结果实际上相当不错。但是,似乎你在Windows中RAND_MAX
可能是32768左右。我说是因为gcc在Linux中抱怨RAND_MAX+1
会导致整数溢出,所以我不得不添加一个强制转换。
所以问题很可能是RAND_MAX
太小或者系统上rand()
的实现不是很好。
如果问题的根源是rand()
的实现,那么您唯一的选择就是从更好的库更改为另一个函数。但是,如果问题是第一个问题,您可以按如下方式解决问题。
/* change `rand()` to return two concatenated rands */
typedef long long rand_type; /* this type depends on your actual system, you might get away with `int` */
#define BIGGER_RAND_MAX ((RAND_MAX + 2) * RAND_MAX)
rand_type bigger_rand(void)
{
return (rand_type)rand() * (RAND_MAX + 1) + rand();
}
然后尝试使用此rand具有更高范围的程序。如果问题仍然存在,很可能是你的rand()
函数远非随机。
旁注:您的random_bool
应该返回bool
,而不是double
!由于您正在检查double
对零,这也可能是问题的根源,因为双重可能不完全为零。
答案 2 :(得分:0)
我认为这个函数的结果与RAND_MAX值有关,在这种情况下p = 1e-6,如果RAND_MAX等于9999那么这将永远不会返回true