Answers:
到目前为止,所有答案在数学上都是错误的。除非将返回间隔的长度除以2(即2的幂),否则返回rand() % N
不会均匀地给出该范围内的数字。此外,人们不知道的模数是否独立:它们可能会走,这是均匀的,但不是很随机。似乎合理的唯一假设是得出泊松分布:相同大小的任何两个不重叠的子间隔都可能相同且独立。对于一组有限的值,这意味着分布均匀,并且还确保的值分散良好。[0, N)
N
rand()
rand()
0, 1, 2, ...
rand()
rand()
这意味着更改范围的唯一正确方法rand()
是将其分成多个框。例如,如果RAND_MAX == 11
并且您想要的范围1..6
,则应将其分配{0,1}
给1,{2,3}
到2,依此类推。这些是不相交的,大小相等的间隔,因此均匀且独立地分布。
使用浮点除法的建议在数学上是合理的,但原则上存在四舍五入的问题。也许double
是足够高的精度才能使其工作;也许不是。我不知道,我也不想弄清楚;无论如何,答案取决于系统。
正确的方法是使用整数算术。也就是说,您需要以下内容:
#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()
,拉n
位(使用random_at_most()
)和回报[0, 2**n)
,然后应用random_at_most()
与random_extended()
到位的random()
(而2**n - 1
代替RAND_MAX
)拉一个随机值小于2**n
,假设你有一个数值类型,它可以保持这样的一个值。最后,当然,您可以[min, max]
使用来获取值min + random_at_most(max - min)
,包括负值。
max - min > RAND_MAX
,它将不起作用,这比我上面提到的问题更严重(例如,VC ++ RAND_MAX
只有32767)。
do {} while()
。
在@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);
}
limit
,可以通过将int(以及可选地bucket
)设置为int 来轻松解决此问题。编辑:我已经提交和编辑提案。RAND_MAX / range
INT_MAX
buckets * range
RAND_MAX
如果您知道某个范围的最大值和最小值,并且想要生成介于该范围之间的数字,则可以使用以下公式:
r = (rand() % (max + 1 - min)) + min
int
溢出max+1-min
。
unsigned 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足够小而不会溢出)。
max + 1
,如果有一个rand() == RAND_MAX
或rand()
非常接近RAND_MAX
和浮点错误将最终的结果过去max + 1
。为了安全起见,您应在返回结果之前检查结果是否在范围内。
RAND_MAX + 1.0
。不过,我仍然不确定这是否足以防止max + 1
退货:特别是,+ min
结尾处的回合可能最终产生max + 1
大量rand()。完全放弃这种方法而使用整数算法。
RAND_MAX
替换RAND_MAX+1.0
为,那么我相信这是安全的,只要+ min
使用整数算术即可完成: return (unsigned int)((max - min + 1) * scaled) + min
。(不明显的)原因是,假设IEEE 754算术和二分之一到四舍五入,(也max - min + 1
可以精确地表示为双精度,但是在典型机器上是正确的),x * scaled < x
对于任何积极的双重x
和任何双重scaled
令人满意0.0 <= scaled && scaled < 1.0
。
randr(0, UINT_MAX)
:总是产生0
您不只是这样做:
srand(time(NULL));
int r = ( rand() % 6 ) + 1;
%
是模运算符。本质上,它将只是除以6并返回余数...从0-5
rand()
包括生成器状态的低位(如果使用LCG)。到目前为止,我还没有看到一个—所有这些(是的,包括MSVC的RAND_MAX仅为32767)都删除了低位。出于其他原因,不建议使用模数,即使用模数会偏向较小的数字。
对于那些了解偏差问题但无法忍受基于拒绝的方法的不可预测的运行时间的人,此系列在[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
通常INT_MAX
是RAND_MAX + 1
-> UB(例如INT_MIN)
RAND_MAX * n
太大,则必须小心避免整数溢出”。您需要安排使用适合您需求的类型。
RAND_MAX
通常INT_MAX
是”是,但是仅在16位系统上!任何合理的现代建筑技术都将以INT_MAX
2 ^ 32/2和2 ^ 16/2 放置RAND_MAX
。这是不正确的假设吗?
int
编译器,我发现RAND_MAX == 32767
一个和RAND_MAX == 2147483647
另一个。我的整体经验(数十年) RAND_MAX == INT_MAX
更多。如此不同意,以至于合理的现代32位体系结构肯定会有RAND_MAX
at 2^16 / 2
。由于C规范允许使用32767 <= RAND_MAX <= INT_MAX
,所以无论如何我还是要编写代码,而不是倾向于编写代码。
为了避免模偏差(在其他答案中建议),您可以始终使用:
arc4random_uniform(MAX-MIN)+MIN
其中“ MAX”是上限,“ MIN”是下限。例如,对于10到20之间的数字:
arc4random_uniform(20-10)+10
arc4random_uniform(10)+10
简单的解决方案,比使用“ rand()%N”更好。
#include <bsd/stdlib.h>
先。另外,有什么想法如何在没有MinGW或CygWin的Windows上获得此功能?
这比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
可以很容易地溢出int
加法。那样的话,(RAND_MAX + 1) % range
会产生可疑的结果。考虑(RAND_MAX + (uint32_t)1)
尽管Ryan是正确的,但根据有关随机性来源的已知信息,解决方案可以简单得多。重述该问题:
[0, MAX)
均匀且分布均匀的。[rmin, rmax]
,其中0 <= rmin < rmax < MAX
。以我的经验,如果箱(或“盒子”)的数量显着小于原始数量的范围,并且原始来源在密码学上很强大-则不需要经历所有的rigamarole,简单的模除法就可以足够(例如output = rnd.next() % (rmax+1)
,如果rmin == 0
),并产生均匀分布的“足够”的随机数,而不会降低速度。关键因素是随机性来源(例如,孩子们,不要在家尝试rand()
)。
这是它在实践中如何工作的示例/证明。我想生成1到22之间的随机数,并具有产生随机字节(基于Intel RDRAND)的强密码源。结果是:
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;
}
我在Mac OS X上使用clang-6.0.1(直接)和gcc-4.8.3使用“ -Wa,q”标志对其进行了编译(因为GAS不支持这些新指令)。
gcc randu.c -o randu -Wa,q
(Ubuntu 16上的GCC 5.3.1)或clang randu.c -o randu
(Clang 3.8.0)兼容,但在运行时使用来转储核心Illegal instruction (core dumped)
。有任何想法吗?
如前所述,模数不足,因为它会使分布偏斜。这是我的代码,它掩盖了位并使用它们来确保分布不偏斜。
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;
我知道模运算比屏蔽运算要慢得多,但是我仍然认为...应该进行测试。
rand()
返回int
范围内的[0..RAND_MAX]
。该范围很容易成为该范围的子范围,uint32_t
因此randomInRange(0, ,b)
永远不会生成该范围内的值(INT_MAX...b]
。