How to generate a random int in C?

后端 未结 27 2068
故里飘歌
故里飘歌 2020-11-22 00:31

Is there a function to generate a random int number in C? Or will I have to use a third party library?

27条回答
  •  北海茫月
    2020-11-22 00:50

    The standard C function is rand(). It's good enough to deal cards for solitaire, but it's awful. Many implementations of rand() cycle through a short list of numbers, and the low bits have shorter cycles. The way that some programs call rand() is awful, and calculating a good seed to pass to srand() is hard.

    The best way to generate random numbers in C is to use a third-party library like OpenSSL. For example,

    #include 
    #include 
    #include 
    #include 
    
    /* Random integer in [0, limit) */
    unsigned int random_uint(unsigned int limit) {
        union {
            unsigned int i;
            unsigned char c[sizeof(unsigned int)];
        } u;
    
        do {
            if (!RAND_bytes(u.c, sizeof(u.c))) {
                fprintf(stderr, "Can't get random bytes!\n");
                exit(1);
            }
        } while (u.i < (-limit % limit)); /* u.i < (2**size % limit) */
        return u.i % limit;
    }
    
    /* Random double in [0.0, 1.0) */
    double random_double() {
        union {
            uint64_t i;
            unsigned char c[sizeof(uint64_t)];
        } u;
    
        if (!RAND_bytes(u.c, sizeof(u.c))) {
            fprintf(stderr, "Can't get random bytes!\n");
            exit(1);
        }
        /* 53 bits / 2**53 */
        return (u.i >> 11) * (1.0/9007199254740992.0);
    }
    
    int main() {
        printf("Dice: %d\n", (int)(random_uint(6) + 1));
        printf("Double: %f\n", random_double());
        return 0;
    }
    

    Why so much code? Other languages like Java and Ruby have functions for random integers or floats. OpenSSL only gives random bytes, so I try to mimic how Java or Ruby would transform them into integers or floats.

    For integers, we want to avoid modulo bias. Suppose that we got some random 4 digit integers from rand() % 10000, but rand() can only return 0 to 32767 (as it does in Microsoft Windows). Each number from 0 to 2767 would appear more often than each number from 2768 to 9999. To remove the bias, we can retry rand() while the value is below 2768, because the 30000 values from 2768 to 32767 map uniformly onto the 10000 values from 0 to 9999.

    For floats, we want 53 random bits, because a double holds 53 bits of precision (assuming it's an IEEE double). If we use more than 53 bits, we get rounding bias. Some programmers write code like rand() / (double)RAND_MAX, but rand() might return only 31 bits, or only 15 bits in Windows.

    OpenSSL's RAND_bytes() seeds itself, perhaps by reading /dev/urandom in Linux. If we need many random numbers, it would be too slow to read them all from /dev/urandom, because they must be copied from the kernel. It is faster to allow OpenSSL to generate more random numbers from a seed.

    More about random numbers:

    • Perl's Perl_seed() is an example of how to calculate a seed in C for srand(). It mixes bits from the current time, the process ID, and some pointers, if it can't read /dev/urandom.
    • OpenBSD's arc4random_uniform() explains modulo bias.
    • Java API for java.util.Random describes algorithms for removing bias from random integers, and packing 53 bits into random floats.

提交回复
热议问题