How to generate a random integer number from within a range

后端 未结 11 1191
隐瞒了意图╮
隐瞒了意图╮ 2020-11-21 23:57

This is a follow on from a previously posted question:

How to generate a random number in C?

I wish to be able to generate a random number from within a part

相关标签:
11条回答
  • 2020-11-22 00:37

    Here is a slight simpler algorithm than Ryan Reich's solution:

    /// Begin and end are *inclusive*; => [begin, end]
    uint32_t getRandInterval(uint32_t begin, uint32_t end) {
        uint32_t range = (end - begin) + 1;
        uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);
    
        /* Imagine range-sized buckets all in a row, then fire randomly towards
         * the buckets until you land in one of them. All buckets are equally
         * likely. If you land off the end of the line of buckets, try again. */
        uint32_t randVal = rand();
        while (randVal >= limit) randVal = rand();
    
        /// Return the position you hit in the bucket + begin as random number
        return (randVal % range) + begin;
    }
    

    Example (RAND_MAX := 16, begin := 2, end := 7)
        => range := 6  (1 + end - begin)
        => limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)
    
    The limit is always a multiple of the range,
    so we can split it into range-sized buckets:
        Possible-rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
        Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
        Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]
    
    1st call to rand() => 13
        → 13 is not in the bucket-range anymore (>= limit), while-condition is true
            → retry...
    2nd call to rand() => 7
        → 7 is in the bucket-range (< limit), while-condition is false
            → Get the corresponding bucket-value 1 (randVal % range) and add begin
        => 3
    
    0 讨论(0)
  • 2020-11-22 00:40

    For those who understand the bias problem but can't stand the unpredictable run-time of rejection-based methods, this series produces a progressively less biased random integer in the [0, n-1] interval:

    r = n / 2;
    r = (rand() * n + r) / (RAND_MAX + 1);
    r = (rand() * n + r) / (RAND_MAX + 1);
    r = (rand() * n + r) / (RAND_MAX + 1);
    ...
    

    It does so by synthesising a high-precision fixed-point random number of i * log_2(RAND_MAX + 1) bits (where i is the number of iterations) and performing a long multiplication by n.

    When the number of bits is sufficiently large compared to n, the bias becomes immeasurably small.

    It does not matter if RAND_MAX + 1 is less than n (as in this question), or if it is not a power of two, but care must be taken to avoid integer overflow if RAND_MAX * n is large.

    0 讨论(0)
  • 2020-11-22 00:40

    While Ryan is correct, the solution can be much simpler based on what is known about the source of the randomness. To re-state the problem:

    • There is a source of randomness, outputting integer numbers in range [0, MAX) with uniform distribution.
    • The goal is to produce uniformly distributed random integer numbers in range [rmin, rmax] where 0 <= rmin < rmax < MAX.

    In my experience, if the number of bins (or "boxes") is significantly smaller than the range of the original numbers, and the original source is cryptographically strong - there is no need to go through all that rigamarole, and simple modulo division would suffice (like output = rnd.next() % (rmax+1), if rmin == 0), and produce random numbers that are distributed uniformly "enough", and without any loss of speed. The key factor is the randomness source (i.e., kids, don't try this at home with rand()).

    Here's an example/proof of how it works in practice. I wanted to generate random numbers from 1 to 22, having a cryptographically strong source that produced random bytes (based on Intel RDRAND). The results are:

    Rnd distribution test (22 boxes, numbers of entries in each box):     
     1: 409443    4.55%
     2: 408736    4.54%
     3: 408557    4.54%
     4: 409125    4.55%
     5: 408812    4.54%
     6: 409418    4.55%
     7: 408365    4.54%
     8: 407992    4.53%
     9: 409262    4.55%
    10: 408112    4.53%
    11: 409995    4.56%
    12: 409810    4.55%
    13: 409638    4.55%
    14: 408905    4.54%
    15: 408484    4.54%
    16: 408211    4.54%
    17: 409773    4.55%
    18: 409597    4.55%
    19: 409727    4.55%
    20: 409062    4.55%
    21: 409634    4.55%
    22: 409342    4.55%   
    total: 100.00%
    

    This is as close to uniform as I need for my purpose (fair dice throw, generating cryptographically strong codebooks for WWII cipher machines such as http://users.telenet.be/d.rijmenants/en/kl-7sim.htm, etc). The output does not show any appreciable bias.

    Here's the source of cryptographically strong (true) random number generator: Intel Digital Random Number Generator and a sample code that produces 64-bit (unsigned) random numbers.

    int rdrand64_step(unsigned long long int *therand)
    {
      unsigned long long int foo;
      int cf_error_status;
    
      asm("rdrand %%rax; \
            mov $1,%%edx; \
            cmovae %%rax,%%rdx; \
            mov %%edx,%1; \
            mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
            *therand = foo;
      return cf_error_status;
    }
    

    I compiled it on Mac OS X with clang-6.0.1 (straight), and with gcc-4.8.3 using "-Wa,q" flag (because GAS does not support these new instructions).

    0 讨论(0)
  • 2020-11-22 00:44

    In order to avoid the modulo bias (suggested in other answers) you can always use:

    arc4random_uniform(MAX-MIN)+MIN
    

    Where "MAX" is the upper bound and "MIN" is lower bound. For example, for numbers between 10 and 20:

    arc4random_uniform(20-10)+10
    
    arc4random_uniform(10)+10
    

    Simple solution and better than using "rand() % N".

    0 讨论(0)
  • 2020-11-22 00:45

    Here is a formula if you know the max and min values of a range, and you want to generate numbers inclusive in between the range:

    r = (rand() % (max + 1 - min)) + min
    
    0 讨论(0)
  • 2020-11-22 00:47

    Will return a floating point number in the range [0,1]:

    #define rand01() (((double)random())/((double)(RAND_MAX)))
    
    0 讨论(0)
提交回复
热议问题