一个范围之间的64位随机数

时间:2013-11-24 13:39:36

标签: c random range bit

所以我一直在寻找一个函数,这些函数接受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));
    }
}

2 个答案:

答案 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)

在这种情况下,我们会选择m264-1n为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;
}