一个有些晚的答案,但如果生成的质量很重要,它应该提供一些额外的信息。(并非所有应用程序都需要这个——轻微的偏差通常不是问题。)
首先,当然,原始代码中的问题在于
range * rand()
优先于后面的除法,并使用整数算术进行计算。根据
RAND_MAX
,这很容易导致溢出,产生实现定义的结果;在我所知道的所有实现中,如果它导致溢出(因为
RAND_MAX > INT_MAX / range
),实际结果几乎肯定小于
RAND_MAX + 1.0
,而除法将导致小于
1.0
的值。有几种避免这种情况的方法:最简单和最可靠的方法是
rand() % range + lowest
。
请注意,这假设
rand()
具有合理的质量。许多早期的实现不是这样的,我看到过至少一个模拟掷骰子的
rand() % 6 + 1
交替奇偶性的实现。唯一正确的解决方案是获得更好的
rand()
实现;这导致人们尝试替代解决方案,例如
(range * (rand() / (RAND_MAX + 1.0))) + lowest
。这掩盖了问题,但它不会将坏的生成器变成好的。
第二个问题是,如果生成的质量很重要,当生成随机整数时,您正在离散化:例如,如果您模拟掷骰子,则有六个可能的值,您希望它们以相等的概率发生。随机生成器将生成
RAND_MAX + 1
个不同的值,并具有相等的概率。如果
RAND_MAX + 1
不是6的倍数,则无法在6个期望值之间均匀分配值。想象一下简单的情况,其中
RAND_MAX + 1
为10。使用上面的
%
方法,值1-4的可能性是值5和6的两倍。如果您使用更复杂的公式
1 + int(6 * (rand() / (RAND_MAX + 1.0)))
(在
RAND_MAX + 1 == 10
的情况下),结果表明3和6只有其他值的一半那么可能。从数学上讲,没有办法将10个不同的值均匀地分配到6个插槽中,每个插槽中具有相同数量的元素。
当然,RAND_MAX
始终比10大得多,并且引入的偏差将会明显减少;如果范围显著小于RAND_MAX
,那么可能是可以接受的。如果不是,通常的处理方法如下:
int limit = (RAND_MAX + 1LL) - (RAND_MAX + 1LL) % range;
// 1LL will prevent overflow on most machines.
int result = rand();
while ( result >= limit ) {
result = rand();
}
return result % range + lowest;
有几种方法可以确定要丢弃的值。这恰好是我使用的方法,但我记得Andy Koenig使用了完全不同的方法,但最终结果是相同的。
请注意,大多数情况下,您不会进入循环; 最坏的情况是range
为(RAND_MAX + 1) / 2 + 1
,即使在这种情况下,您仍然平均只需循环一次。
请注意,这些注释仅适用于需要固定数量的离散结果的情况。对于常见的(其他)生成介于[0,1)
范围内的随机浮点数的情况,rand() / (RAND_MAX + 1.0)
是您能够获得的最佳结果。
(rand() % range) + lowest
的方法吗? - Some programmer dude