使用getrandom在C中随机浮动

时间:2016-03-10 21:09:26

标签: c random

我正在尝试在0和1之间生成一个随机浮点数(无论是[0,1]还是[0,1],对我来说都不重要)。关于此问题的在线问题似乎涉及rand()调用,种子为time(NULL),但我希望能够每秒多次调用我的程序并且每次都获得不同的随机数。这引导我进入Linux中的getrandom系统调用,它来自/ dev / urandom。我想出了这个:

#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdint.h>

int main() {
  uint32_t r = 0;
  for (int i = 0; i < 20; i++) {
    syscall(SYS_getrandom, &r, sizeof(uint32_t), 0);
    printf("%f\n", ((double)r)/UINT32_MAX);
  }
  return 0;
}

我的问题只是我是否正确地这样做了。它似乎有效,但我担心我会误用某些东西,并且在网上没有使用getrandom()的例子。

2 个答案:

答案 0 :(得分:8)

OP有两个问题:

  1. 如何非常随机地启动序列。

  2. 如何在[0 ... 1)范围内生成double

  3. 通常的方法是采用非常随机的来源,例如/dev/urandom或来自syscall()或甚至seed = time() ^ process_id;的结果,并通过srand()播种。然后根据需要致电rand()

    下面包含一个快速转换的方法来生成统一[0.0 to 1.0)(线性分布)。但是像所有随机生成函数一样,真正好的函数基于广泛的研究。这个只需根据rand()DBL_MANT_DIG调用RAND_MAX几次,

    [编辑]原始double rand_01(void)有一个缺点,即它只生成2 ^ 52个不同的double而不是2 ^ 53。它已被修改。替代方案:远远低于double的{​​{1}}版本。

    rand_01_ld(void)

    如果想要扩展到更宽的FP,则无符号宽整数类​​型可能不足。以下是一种没有这种限制的便携式方法。

    #include <assert.h>
    #include <float.h>
    #include <limits.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    double rand_01(void) {
      assert(FLT_RADIX == 2); // needed for DBL_MANT_DIG
      unsigned long long limit = (1ull << DBL_MANT_DIG) - 1;
      double r = 0.0;
      do {
        r += rand();
        // Assume RAND_MAX is a power-of-2 - 1
        r /= (RAND_MAX/2 + 1)*2.0;
        limit = limit / (RAND_MAX/2 + 1) / 2;
      } while (limit);
    
      // Use only DBL_MANT_DIG (53) bits of precision.
      if (r < 0.5) {
        volatile double sum = 0.5 + r;
        r = sum - 0.5;
      }
      return r;
    }
    
    int main(void) {
      FILE *istream = fopen("/dev/urandom", "rb");
      assert(istream);
      unsigned long seed = 0;
      for (unsigned i = 0; i < sizeof seed; i++) {
        seed *= (UCHAR_MAX + 1);
        int ch = fgetc(istream);
        assert(ch != EOF);
        seed += (unsigned) ch;
      }
      fclose(istream);
      srand(seed);
    
      for (int i=0; i<20; i++) {
        printf("%f\n", rand_01());
      }
    
      return 0;
    }
    

答案 1 :(得分:5)

如果您需要生成双打,可以使用以下算法:

CPython generates random numbers使用以下算法( I 更改了函数名,typedef和返回值,但算法保持不变):

double get_random_double() {
    uint32_t a = get_random_uint32_t() >> 5;
    uint32_t b = get_random_uint32_t() >> 6;
    return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
}

该算法的来源是Takuji Nishimura和Makoto Matsumoto的Mersenne Twister 19937随机数发生器。遗憾的是,源中提到的原始链接不再可供下载。

CPython中对此功能的评论注意到以下内容:

  

[this function]是原始代码中名为genrand_res53的函数;   在[0,1]上生成一个53位分辨率的随机数;注意   9007199254740992 == 2**53;我假设他们拼写为“/2**53”   在编译器所希望的(可能是徒劳的)希望中相乘   在编译时优化分部。 671088642**26。在   效果,a包含27个随机位,左移26,b填充   低于此位的53位分子的26位。

     

原始代码将Isaku Wada归功于此算法,2002/01/09

从该代码中简化,如果您想快速创建float,则应使用uint32_t屏蔽(1 << FLT_MANT_DIG) - 1的位并除以(1 << FLT_MANT_DIG)以获得正确的值[0, 1)间隔:

#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdint.h>
#include <float.h>

int main() {
    uint32_t r = 0;
    float result;
    for (int i = 0; i < 20; i++) {
        syscall(SYS_getrandom, &r, sizeof(uint32_t), 0);
        result = (float)(r & ((1 << FLT_MANT_DIG) - 1)) / (1 << FLT_MANT_DIG);
        printf("%f\n", result);
    }
    return 0;
}

由于可以假设您的Linux有C99编译器,我们可以使用ldexpf代替该分区:

#include <math.h>

result = ldexpf(r & ((1 << FLT_MANT_DIG) - 1), -FLT_MANT_DIG);

要获得关闭间隔[0, 1],您可以做效率稍低的

result = ldexpf(r % (1 << FLT_MANT_DIG), -FLT_MANT_DIG);

为了快速生成大量高质量的随机数,我只需使用系统调用来获取足够的数据来播种PRNG或CPRNG,然后从那里开始。