【问题标题】:64bit random number between a range一个范围之间的 64 位随机数
【发布时间】:2013-11-25 01:38:38
【问题描述】:

所以几天来我一直在寻找一个函数,它需要 2 个参数一个低值和一个高值(都是 64 位整数),而不是在这些范围之间生成一个随机数。我一直遇到的问题是这个数字不是 64 位整数。或者边缘的数字比中间的数字更常见。

这是一些代码:它只是不断返回 -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));
    }
}

【问题讨论】:

  • 不确定您的代码示例打算做什么...看看math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt64.html
  • 它不允许我把它放在一个非常烦人的范围内......
  • random_value % (rangeend - rangestart) + rangestart
  • @keltar 仅当random_value 具有非常高的精度时才有效。明显超过 64 位。所以这很难使用标准c实现。还有整数溢出的风险。例如考虑有符号整数,其中rangeend 具有最大值,rangestart the minimum value
  • 搜索一个好的第三方随机数生成器库,它有一个内置函数。放弃rand(),它已经坏了,无法修复。

标签: c random range bit


【解决方案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 >> log(N) 以均匀分布;那么只需丢弃 rand_orig() 中大于 N 的最大倍数的值并用模数减小范围即可。

另一种方法是创建一种方法,将值(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) rand_orig 到底是什么?它返回什么范围?如果rand_orig 返回的最大值小于N - 1,则第一个示例被破坏。 2) 您的最后一个样本没有声明ypco_prime。它甚至从未初始化pco_prime。在我看来,它在概念上也被打破了。
  • 感谢 cmets; rand_orig 可能是一些库函数,它在 0..2^n-1 范围内产生均匀分布,其中 2^n-1 > N。添加到 x 的数字是 N 的互质数至关重要;但如果它的大小与 N 大致相同,则可以使用素数。
【解决方案2】:

您的代码返回 0 或 -1,因为 18446744073709551614 太大而无法放入 int64_t。 (实际上,uint64_t 放不下它有点大,因为它正好是 264,而 k 位无符号整数可以放的最大数是 2k-1.) 所以你最终会出现有符号整数溢出。 (gcc 和 clang(至少)警告过你这一点,即使没有 -Wall。)

无论如何,只要你有一些机制来生成随机的 64 位无符号整数,生成你正在寻找的库函数并不难。一个不错的选择是Mersenne Twister library。但是,为了演示,我们只能使用标准 C 库函数,在本例中为lrand48,它会在(0, 2<sup>31</sup>-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 的范围大小为 264,因此我们需要排除 mod<sub>high-low</sub>2<sup>64</sup> 值。不幸的是,除非我们有一个长度超过 64 位的无符号整数,否则我们实际上无法直接计算模数。但是,我们可以使用身份:

mod<sub>k</sub>(mod<sub>k</sub>m + mod<sub>k</sub>n) &amp;equals; mod<sub>k</sub>(m+n).

在这种情况下,我们将选择m 作为2<sup>64</sup>-1n 作为1,以避免必须计算modhigh-lown。此外,很容易证明,除非k 是 2 的精确幂,否则mod<sub>k</sub>2<sup>64</sup>-1 + mod<sub>k</sub>1 不可能恰好是 k,而如果 k 是 2 的精确幂,则所需的 mod<sub>k</sub>2<sup>64</sup> 是 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;
}

请注意,在最坏的情况下,要排除的值的数量为 263-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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    • 1970-01-01
    • 2021-06-22
    相关资源
    最近更新 更多