简体   繁体   English

使用getrandom在C中随机浮点数

[英]Random float in C using getrandom

I'm trying to generate a random floating point number in between 0 and 1 (whether it's on [0,1] or [0,1) shouldn't matter for me).我正在尝试生成一个介于 0 和 1 之间的随机浮点数(无论是在 [0,1] 还是 [0,1)上对我来说都无关紧要)。 Every question online about this seems to involves the rand() call, seeded with time(NULL) , but I want to be able to invoke my program more than once a second and get different random numbers every time.网上关于此的每个问题似乎都涉及rand()调用,以time(NULL)种子,但我希望能够每秒多次调用我的程序并每次获得不同的随机数。 This lead me to the getrandom syscall in Linux, which pulls from /dev/urandom.这让我想到了 Linux 中的 getrandom 系统调用,它从 /dev/urandom 中提取。 I came up with this:我想出了这个:

#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;
}

My question is simply whether or not I'm doing this correctly.我的问题只是我是否正确地做到了这一点。 It appears to work, but I'm worried that I'm misusing something, and there are next to no examples using getrandom() online.它似乎有效,但我担心我滥用了某些东西,而且几乎没有在线使用 getrandom() 的示例。

OP has 2 issues: OP有两个问题:

  1. How to started the sequence very randomly.如何非常随机地开始序列。

  2. How to generate a double on the [0...1) range.如何在 [0...1) 范围内生成double精度值。

The usual method is to take a very random source like /dev/urandom or the result from the syscall() or maybe even seed = time() ^ process_id;通常的方法是采用非常随机的来源,如/dev/urandomsyscall()的结果,甚至可能是seed = time() ^ process_id; and seed via srand() .和种子通过srand() Then call rand() as needed.然后根据需要调用rand()

Below includes a quickly turned method to generate a uniform [0.0 to 1.0) (linear distribution).下面包括一种快速转向的方法来生成均匀的[0.0 to 1.0) (线性分布)。 But like all random generating functions, really good ones are base on extensive study.但就像所有随机生成函数一样,真正好的函数是基于广泛的研究。 This one simply calls rand() a few times based on DBL_MANT_DIG and RAND_MAX ,这个只是基于DBL_MANT_DIGRAND_MAX调用了几次rand()

[Edit] Original double rand_01(void) has a weakness in that it only generates a 2^52 different double s rather than 2^53. [编辑] 原来的double rand_01(void)有一个弱点,它只生成一个 2^52 不同的double而不是 2^53。 It has been amended.它已被修改。 Alternative: a double version of rand_01_ld(void) far below.替代方案:远低于rand_01_ld(void)double版本。

#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;
}

If one wanted to extend to an even wider FP, unsigned wide integer types may be insufficient.如果想要扩展到更宽的 FP,无符号宽整数类​​型可能是不够的。 Below is a portable method that does not have that limitation.下面是一种没有这种限制的便携式方法。

long double rand_01_ld(void) {
  // These should be calculated once rather than each function call
  // Leave that as a separate implementation problem
  // Assume RAND_MAX is power-of-2 - 1
  assert((RAND_MAX & (RAND_MAX + 1U)) == 0);
  double rand_max_p1 = (RAND_MAX/2 + 1)*2.0;
  unsigned BitsPerRand = (unsigned) round(log2(rand_max_p1));
  assert(FLT_RADIX != 10);
  unsigned BitsPerFP = (unsigned) round(log2(FLT_RADIX)*LDBL_MANT_DIG);

  long double r = 0.0;
  unsigned i;
  for (i = BitsPerFP; i >= BitsPerRand; i -= BitsPerRand) {
    r += rand();
    r /= rand_max_p1;
  }
  if (i) {
    r += rand() % (1 << i);
    r /= 1 << i;
  }
  return r;
}

If you need to generate doubles, the following algorithm could be of use:如果您需要生成双打,可以使用以下算法:

CPython generates random numbers using the following algorithm ( I changed the function name, typedefs and return values, but algorithm remains the same): CPython 使用以下算法生成随机数更改了函数名称、类型定义和返回值,但算法保持不变):

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);
}

The source of that algorithm is a Mersenne Twister 19937 random number generator by Takuji Nishimura and Makoto Matsumoto.该算法的来源是 Takuji Nishimura 和 Makoto Matsumoto 的 Mersenne Twister 19937 随机数生成器。 Unfortunately the original link mentioned in the source is not available for download any longer.不幸的是,源中提到的原始链接不再可供下载。

The comment on this function in CPython notes the following: CPython 中对此函数的注释指出以下内容:

[this function] is the function named genrand_res53 in the original code; 【本函数】原代码中名为genrand_res53的函数; generates a random number on [0,1) with 53-bit resolution;在 [0,1) 上以 53 位分辨率生成随机数; note that 9007199254740992 == 2**53 ;请注意9007199254740992 == 2**53 I assume they're spelling " /2**53 " as multiply-by-reciprocal in the (likely vain) hope that the compiler will optimize the division away at compile-time.我假设他们将“ /2**53 ”拼写为乘以倒数(可能是徒劳的)希望编译器将在编译时优化除法。 67108864 is 2**26 . 671088642**26 In effect, a contains 27 random bits shifted left 26, and b fills in the lower 26 bits of the 53-bit numerator.实际上,a 包含 27 个左移 26 位的随机位, b填充 53 位分子的低 26 位。

The orginal code credited Isaku Wada for this algorithm, 2002/01/09该算法的原始代码归功于 Isaku Wada,2002/01/09


Simplifying from that code, if you want to create a float fast, you should mask the bits of uint32_t with (1 << FLT_MANT_DIG) - 1 and divide by (1 << FLT_MANT_DIG) to get the proper [0, 1) interval:从该代码简化,如果你想快速创建一个float ,你应该用(1 << FLT_MANT_DIG) - 1屏蔽uint32_t的位并除以(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;
}

Since it can be assumed that your Linux has a C99 compiler, we can use ldexpf instead of that division:由于可以假设您的 Linux 具有 C99 编译器,因此我们可以使用ldexpf代替该分区:

#include <math.h>

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

To get the closed interval [0, 1] , you can do the slightly less efficient要获得闭区间[0, 1] ,您可以稍微降低效率

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

To generate lots of good quality random numbers fast, I'd just use the system call to fetch enough data to seed a PRNG or CPRNG, and proceed from there.为了快速生成大量高质量的随机数,我只是使用系统调用来获取足够的数据来播种 PRNG 或 CPRNG,然后从那里开始。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM