所以我一直在寻找一个函数,这些函数接受2个参数的低值和高值(两个都是64位整数),而不是在这些范围之间生成一个随机数。我遇到的问题是这个数字不是64位int。或者边缘的数字比中间的数字更常见。
这是一些代码:它只是返回-1或0 ......
#include<stdio.h>
#include<stdlib.h>
#include<inttypes.h>
int64_t range1=0,range2=18446744073709551614;
int64_t getRandomInRange(int64_t low, int64_t high )
{
int64_t base_random = rand();
if (RAND_MAX==base_random) return getRandomInRange(low, high);
int range = high-low,
remainder = RAND_MAX%range,
bucket = RAND_MAX/range;
if (base_random < RAND_MAX-remainder) {
return low+base_random/bucket;
} else {
return getRandomInRange(low, high);
}
}
int main () {
int i;
for (i=0;i<100;i++) {
printf("random number: %lld\n",getRandomInRange(range1, range2));
}
}
答案 0 :(得分:1)
取模N不会导致均匀分布,除非N将R范围精确地划分为:
rnd = 0..15, range = 9.
0 1 2 3 4 5 6 7 8 <-- 0..8 % 9
0 1 2 3 4 5 6 <-- 9-15 % 9
----------------------------------
2 2 2 2 2 2 2 1 1 <-- sum = 16
同样,如果一个人试图通过乘以例如9/16
rnd = 0..15, range = 9, reducing function = rnd * 9 >> 4, one has
0 1 2 3 4 5 6 7 8 for rnd = 0, 2, 4, 6, 8, 9, 13, 15 and
0 1 2 3 5 6 7 for rnd = 1, 3, 5, 7, 10, 12, 14
------------------------
2 2 2 2 1 2 2 2 1 <-- sum = 16
这就是所谓的“鸽子洞原则”。
创建随机数均匀分布的一种正确方法是生成随机数的ceil(log2(N))位,直到由位表示的数字小于范围:
int rand_orig(); // the "original" random function returning values from 0..2^n-1
// We assume that n = ceil(log2(N));
int rand(int N)
{
int y;
do {
y = rand_orig();
} while (y >= N);
return y;
}
如果rand_orig();当然可以改进。将返回多更大的值n&gt;&gt; log(N)均匀分布;那么只丢弃那些大于N的最大倍数的rand_orig()值并用模数减少范围就足够了。
另一种方法是创建一种方法,将值(N>范围)均匀地平衡到所有桶,例如
#define CO_PRIME 1 // Better to have some large prime 2^(n-1) < CO_PRIME < 2^n-1
int rand_orig(); // some function returning random numbers in range 0..2^n-1
int rand(int N) // N is the range
{
static int x;
int y = rand_orig();
int new_rand = (x + y) % N;
x = (x + CO_PRIME) % N;
return new_rand;
}
现在,这个平衡期x
的周期为N,导致至少均匀分布。
答案 1 :(得分:0)
您的代码返回0或-1,因为18446744073709551614
太大而无法放入int64_t
。 (事实上,它有点太大而不适合uint64_t
,因为它恰好是2 64 ,并且可以放入k
位无符号整数的最大数字是2 k -1。)所以最终会出现有符号整数溢出。 (gcc和clang(至少)警告过你,即使没有-Wall
。)
无论如何,生成你正在寻找的库函数并不是那么困难,前提是你有一些生成随机64位无符号整数的机制。一个很好的选择是Mersenne Twister library。但是,对于演示,我们只能使用标准C库函数,在这种情况下lrand48
,它会在(0, 231-1)
范围内生成一个均匀分布的整数。由于该范围仅产生31位随机性,因此我们需要多次调用它才能产生64位。
#define _XOPEN_SOURCE
#include <stdlib.h>
#include <stdint.h>
uint64_t urand64() {
uint64_t hi = lrand48();
uint64_t md = lrand48();
uint64_t lo = lrand48();
return (hi << 42) + (md << 21) + lo;
}
要获得[low, high)
范围内的无偏样本,我们需要将随机数生成限制为high - low
的某个倍数。范围urand64
的大小为2 64 ,因此我们需要排除modhigh-low264
个值。不幸的是,除非我们有一个长于64位的无符号整数,否则我们实际上无法直接计算模数。但是,我们可以使用身份:
modk(modkm + modkn) = modk(m+n)
。
在这种情况下,我们会选择m
为264-1
和n
为1,以避免计算modhigh-lown
。此外,很容易证明,除非k
的精确幂为2,否则modk264-1 + modk1
不可能精确k
,而如果k
的精确幂为2 ,期望的modk264
为0.我们可以使用以下简单测试来获得2的幂,其解释可以在其他地方找到:
bool is_power_of_2(uint64_t x) {
return x == x & -x;
}
所以我们可以定义:
uint64_t unsigned_uniform_random(uint64_t low, uint64_t high) {
static const uint64_t M = ~(uint64_t)0;
uint64_t range = high - low;
uint64_t to_exclude = is_power_of_2(range) ? 0
: M % range + 1;
uint64_t res;
// Eliminate `to_exclude` possible values from consideration.
while ((res = urand64()) < to_exclude) {}
return low + res % range;
}
请注意,在最坏的情况下,要排除的值的数量是2 63 -1,这略小于可能值范围的一半。因此,在最糟糕的情况下,我们会在找到满意的值之前平均要求两次拨打urand64
。
最后,我们需要处理这样一个事实,即我们被要求生成有符号整数,而不是无符号整数。但是,这不是问题,因为必要的转换是明确定义的。
int64_t uniform_random(int64_t low, int64_t high) {
static const uint64_t OFFSET = ((uint64_t)1) << 63;
uint64_t ulow = (uint64_t)low + OFFSET;
uint64_t uhigh = (uint64_t)high + OFFSET;
uint64_t r = unsigned_uniform_random(ulow, uhigh);
// Conform to the standard; a good compiler should optimize.
if (r >= OFFSET) return r - OFFSET;
else return (int64_t)r - (int64_t)(OFFSET - 1) - 1;
}