Random float in C using getrandom

前端 未结 2 834
说谎
说谎 2021-02-19 15:35

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). Every question online about this seems to i

2条回答
  •  不思量自难忘°
    2021-02-19 16:27

    OP has 2 issues:

    1. How to started the sequence very randomly.

    2. How to generate a double on the [0...1) range.

    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; and seed via srand(). Then call rand() as needed.

    Below includes a quickly turned method to generate a uniform [0.0 to 1.0) (linear distribution). 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,

    [Edit] Original double rand_01(void) has a weakness in that it only generates a 2^52 different doubles rather than 2^53. It has been amended. Alternative: a double version of rand_01_ld(void) far below.

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

提交回复
热议问题