N
不能被 rand()
返回的区间长度整除(即是 2 的幂),那么返回 rand() % N
不会均匀地给出在范围 [0, N)
中的数字。此外,人们不知道 rand()
的模数是否独立:它们可能是 0, 1, 2, ...
,这是均匀但不太随机的。唯一似乎合理的假设是 rand()
产生 Poisson 分布:相同大小的任意两个不重叠子区间等可能且独立。对于有限的值集,这意味着均匀分布,也确保了 rand()
的值被良好地散布。rand()
的范围的唯一正确方法是将其分成盒子;例如,如果 RAND_MAX == 11
并且您想要一个范围为 1..6
,您应将{0,1}
分配给1,将{2,3}
分配给2,以此类推。这些是不相交的、大小相等的区间,因此是均匀和独立分布的。#include <stdlib.h> // For random(), RAND_MAX
// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
unsigned long
// max <= RAND_MAX < ULONG_MAX, so this is okay.
num_bins = (unsigned long) max + 1,
num_rand = (unsigned long) RAND_MAX + 1,
bin_size = num_rand / num_bins,
defect = num_rand % num_bins;
long x;
do {
x = random();
}
// This is carefully written not to overflow
while (num_rand - defect <= (unsigned long)x);
// Truncated division is intentional
return x/bin_size;
}
循环是为了获得完全均匀的分布。例如,如果你收到的随机数是0到2,并且你只想要0到1的数字,那么你就不断取数直到没有得到2;很容易检查这样做可以等概率地得到0或1。虽然编码方式不同,但这种方法也在nos提供的链接中描述了。我使用random()
而不是rand()
,因为它有更好的分布(正如rand()
的手册所指出的)。[0,RAND_MAX]
,那么您必须做一些巧妙的事情。也许最方便的方法是定义一个函数random_extended()
,使用random_at_most()
来拉取n
位并返回[0,2 ** n)
,然后将random_extended()
代替random()
使用random_at_most()
(并将2 ** n - 1
代替RAND_MAX
)来拉取小于2 ** n
的随机值,假设您有一个可以保存这样的值的数值类型。最后,当然,您可以使用min + random_at_most(max - min)
,包括负值,来获得[min,max]
中的值。max - min > RAND_MAX
时,这种方法将无法正常工作,这比我之前提到的问题更为严重(例如 VC++ 的 RAND_MAX
只有32767)。 - interjaydo {} while()
。 - theJPster在@Ryan Reich的回答之后,我想提供我的优化版本。鉴于第二个边界检查,第一个边界检查是不必要的,我将其改为迭代而非递归。它返回[min,max]范围内的值,其中 max >= min
并且 1 + max-min < RAND_MAX
。
unsigned int rand_interval(unsigned int min, unsigned int max)
{
int r;
const unsigned int range = 1 + max - min;
const unsigned int buckets = RAND_MAX / range;
const unsigned int limit = buckets * range;
/* Create equal size buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
do
{
r = rand();
} while (r >= limit);
return min + (r / buckets);
}
如果你知道一个范围的最大和最小值,且想要生成包含在该范围内的数值,那么可以使用以下公式:
r = (rand() % (max + 1 - min)) + min
max+1-min
可能会发生 int
溢出。 - chux - Reinstate Monicaunsigned int
randr(unsigned int min, unsigned int max)
{
double scaled = (double)rand()/RAND_MAX;
return (max - min +1)*scaled + min;
}
查看这里以获取其他选项。
(((max-min+1)*rand())/RAND_MAX)+min
并获得可能完全相同的分布(假设 RAND_MAX 相对于 int 足够小而不会溢出)。 - user180247rand()
等于RAND_MAX
,或者rand()
非常接近RAND_MAX
而浮点误差将最终结果推到max+1
,那么它可能会(非常少见地)返回max+1
。为了安全起见,在返回结果之前,您应该检查结果是否在范围内。 - Mark DickinsonRAND_MAX + 1.0
的说法。然而,我仍不确定这是否足以避免max + 1
的返回:特别是最后的+ min
涉及舍入,可能会在rand()的大值产生max + 1
。完全放弃这种方法,使用整数算术更为安全。 - Mark DickinsonRAND_MAX
替换为RAND_MAX + 1.0
,我认为只要使用整数算术来执行 + min
,就是安全的:return (unsigned int)((max - min + 1) * scaled) + min
。这个(不显然的)原因是,在假定IEEE 754算术和四舍六入的情况下(并且也假设max-min+1
可以被一个双精度表示,但在一台典型的机器上这通常成立),对于任何正的双精度x
和任何满足0.0 <= scaled && scaled < 1.0
的双精度scaled
,总是成立 x * scaled < x
。 - Mark Dickinsonrandr(0, UINT_MAX)
失败:总是生成 0。 - chux - Reinstate Monica那么你就可以这样做:
srand(time(NULL));
int r = ( rand() % 6 ) + 1;
%
是模数运算符。它会将数字除以6并返回余数...从0-5。
rand()
函数的libc库,其中包含生成器状态的低位(如果它使用LCG)。到目前为止,我还没有看到过这样的库——所有这些库(是的,包括MSVC并且RAND_MAX只有32767)都会移除低位。使用取模运算不是推荐的方法,因为它会导致分布偏向于较小的数字。 - Joey对于那些了解偏差问题但无法忍受基于拒绝方法的不可预测运行时间的人来说,此系列在[0,n-1]
区间中产生逐渐减少的偏差随机整数:
r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...
i * log_2(RAND_MAX + 1)
(其中 i
是迭代次数),并通过 n
进行长乘法运算来实现。当比特数与 n
相比足够大时,偏差变得微不足道。无论 RAND_MAX + 1
是否小于 n
(如在此问题中),或者它是否是2的幂,都没有关系,但如果 RAND_MAX * n
很大,则必须注意避免整数溢出。RAND_MAX * n
很大,则必须小心避免整数溢出”。您需要安排使用适当类型来满足您的要求。 - sh1int
编译器,发现其中一个的RAND_MAX == 32767
,而另一个则是RAND_MAX == 2147483647
。我的总体经验(几十年)是RAND_MAX == INT_MAX
更为常见。因此,我不同意合理的现代32位架构一定会有一个RAND_MAX
在2^16 / 2
。由于C规范允许32767 <= RAND_MAX <= INT_MAX
,所以我仍然按照这个范围编码,而不是趋势。 - chux - Reinstate MonicaRAND_MAX == 32767
和 gcc-4.9.3-1.i686 RAND_MAX == 2147483647
。 - chux - Reinstate Monica以下是比Ryan Reich的解决方案稍微简单的算法:
/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
uint32_t range = (end - begin) + 1;
uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);
/* Imagine range-sized buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
uint32_t randVal = rand();
while (randVal >= limit) randVal = rand();
/// Return the position you hit in the bucket + begin as random number
return (randVal % range) + begin;
}
Example (RAND_MAX := 16, begin := 2, end := 7)
=> range := 6 (1 + end - begin)
=> limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)
The limit is always a multiple of the range,
so we can split it into range-sized buckets:
Possible-rand-output: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Buckets: [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
Buckets + begin: [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]
1st call to rand() => 13
→ 13 is not in the bucket-range anymore (>= limit), while-condition is true
→ retry...
2nd call to rand() => 7
→ 7 is in the bucket-range (< limit), while-condition is false
→ Get the corresponding bucket-value 1 (randVal % range) and add begin
=> 3
RAND_MAX + 1
can readily overflow int
addition. In that case, (RAND_MAX + 1) % range
will generate questionable results. Consider (RAND_MAX + (uint32_t)1)
- chux - Reinstate Monicaarc4random_uniform(MAX-MIN)+MIN
"MAX"代表上限,"MIN"代表下限。例如,对于介于10和20之间的数字:
arc4random_uniform(20-10)+10
arc4random_uniform(10)+10
这是一个比使用 "rand() % N" 更好的简单解决方案。
#include <bsd/stdlib.h>
。还有,你知道如何在Windows上获得此功能而无需使用MinGW或CygWin吗? - cat[0, MAX)
,分布均匀。
- 目标是产生均匀分布的随机整数数字,范围在[rmin, rmax]
之间,其中0 <= rmin < rmax < MAX
。output = rnd.next() % (rmax+1)
,如果rmin == 0
),并产生分布“足够”均匀的随机数,而且速度不会有任何损失。关键因素是随机性源(即,孩子们,在家里不要尝试使用rand()
)。
Rnd distribution test (22 boxes, numbers of entries in each box):
1: 409443 4.55%
2: 408736 4.54%
3: 408557 4.54%
4: 409125 4.55%
5: 408812 4.54%
6: 409418 4.55%
7: 408365 4.54%
8: 407992 4.53%
9: 409262 4.55%
10: 408112 4.53%
11: 409995 4.56%
12: 409810 4.55%
13: 409638 4.55%
14: 408905 4.54%
15: 408484 4.54%
16: 408211 4.54%
17: 409773 4.55%
18: 409597 4.55%
19: 409727 4.55%
20: 409062 4.55%
21: 409634 4.55%
22: 409342 4.55%
total: 100.00%
这对于我的目的来说已经足够均匀了(公正的骰子投掷,生成二战密码机的加密强度代码本,例如 http://users.telenet.be/d.rijmenants/en/kl-7sim.htm等)。输出不显示任何明显倾向性。
以下是产生加密强度(真实)随机数发生器的来源: 英特尔数字随机数发生器 以及一个可生成64位(无符号)随机数的示例代码。
int rdrand64_step(unsigned long long int *therand)
{
unsigned long long int foo;
int cf_error_status;
asm("rdrand %%rax; \
mov $1,%%edx; \
cmovae %%rax,%%rdx; \
mov %%edx,%1; \
mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
*therand = foo;
return cf_error_status;
}
我使用clang-6.0.1(直接编译)和gcc-4.8.3(使用“-Wa,q”标志,因为GAS不支持这些新指令)在Mac OS X上进行了编译。
gcc randu.c -o randu -Wa,q
(Ubuntu 16上的GCC 5.3.1)或clang randu.c -o randu
(Clang 3.8.0)编译可以通过,但在运行时会出现“非法指令(core dumped)”错误。有什么想法吗? - cat如前所述,模除不足以满足需求,因为它会扭曲分布。这是我的代码,它屏蔽位并使用它们来确保分布不会偏斜。
static uint32_t randomInRange(uint32_t a,uint32_t b) {
uint32_t v;
uint32_t range;
uint32_t upper;
uint32_t lower;
uint32_t mask;
if(a == b) {
return a;
}
if(a > b) {
upper = a;
lower = b;
} else {
upper = b;
lower = a;
}
range = upper - lower;
mask = 0;
//XXX calculate range with log and mask? nah, too lazy :).
while(1) {
if(mask >= range) {
break;
}
mask = (mask << 1) | 1;
}
while(1) {
v = rand() & mask;
if(v <= range) {
return lower + v;
}
}
}
int main() {
unsigned long long int i;
unsigned int n = 10;
unsigned int numbers[n];
for (i = 0; i < n; i++) {
numbers[i] = 0;
}
for (i = 0 ; i < 10000000 ; i++){
uint32_t rand = random_in_range(0,n - 1);
if(rand >= n){
printf("bug: rand out of range %u\n",(unsigned int)rand);
return 1;
}
numbers[rand] += 1;
}
for(i = 0; i < n; i++) {
printf("%u: %u\n",i,numbers[i]);
}
}
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
我知道取模是比掩码操作要慢得多的,但我仍然认为.....应该进行测试。 - Øystein Schønning-Johansenrand()
返回一个在 [0..RAND_MAX]
范围内的 int
。该范围可以很容易地成为 uint32_t
的子范围,然后 randomInRange(0, b)
不会生成 (INT_MAX...b]
范围内的值。 - chux - Reinstate Monica