C中的rand()函数是否有限制?

时间:2018-12-23 00:03:16

标签: c

这是示例代码

int x = rand() % 90000;

当我做这样的事情时,我意识到所有数字都在0-30000左右。 这有限制吗?如果有我该如何无限制地使用它?

1 个答案:

答案 0 :(得分:0)

您不应使用rand(),因为它在许多C标准库实现中都非常差。它将返回一个介于0和RAND_MAX之间的伪随机数,包括0和RAND_MAX,但是RAND_MAX通常相对较小。例如32767。

如果范围是生成器函数可以返回的值范围的很大一部分,则使用模运算符来产生整数范围是有问题的,因为分布并不完全均匀。

例如,假设rand() % 40000是59999,而我们使用了rand()。结果介于0和19999之间的概率为67%,但介于20000和39999之间的概率仅为33%。这是因为rand()在[0,19999]中以1/3的概率[20000, 39999]的概率为1/3,[40000..59999]的概率为1/3;但是最后三分之一折回去,以便在取模后得到[0,19999]!

在小范围内,偏差不太明显。

我个人希望生成足够的随机位来覆盖所需的范围,然后使用排除方法来选择值。

如果需要使用atleast,则可以使用以下帮助函数来生成伪随机数,其范围至少为#include <inttypes.h> static inline uint64_t rand_atleast(uint64_t atleast) { uint64_t result = 0; do { result = ((uint64_t)RAND_MAX + 1) * result + (uint64_t)rand(); atleast /= ((uint64_t)RAND_MAX + 1); } while (atleast > 0); return result; } (但可以更大;即,它可以返回更大的值) :

int

要使用排除方法在所需范围内创建struct range_spec { uint64_t mask; uint64_t limit; int base; }; static inline void set_range(struct range_spec *spec, int minimum, int maximum) { uint64_t mask; int base; if (minimum <= maximum) { base = minimum; mask = maximum - minimum; } else { base = maximum; mask = minimum - maximum; } spec->base = base; spec->limit = mask; mask |= mask >> 1; mask |= mask >> 2; mask |= mask >> 4; mask |= mask >> 8; mask |= mask >> 16; mask |= mask >> 32; spec->mask = mask; } static inline int rand_range(const struct range_spec *spec) { const uint64_t mask = spec->mask; const uint64_t limit = spec->limit; uint64_t result; do { result = rand_atleast(mask) & mask; } while (result > limit); return spec->base + result; } ,我们可以使用一种结构来包含所需的内容,可以使用一个辅助函数来初始化该范围(以描述某个特定的int范围),另一个辅助函数来生成该范围内的整数:

#ifndef   RNG64_H
#define   RNG64_H
#include <inttypes.h>
#include <time.h>

typedef struct {
    uint64_t  limit;
    int64_t   base;
    int       shift;
} rng64_intrange_spec;

static uint64_t  rng64_state = 1;

static inline uint64_t  rng64(void)
{
    uint64_t  x = rng64_state;
    x ^= x >> 12;
    x ^= x << 25;
    x ^= x >> 27;
    rng64_state = x;
    return x * UINT64_C(2685821657736338717);
}

static inline uint64_t  rng64_randomize(void)
{
    uint64_t  x;
    int       n = 1000;

    x = ((uint64_t)time(NULL) * UINT64_C(19076794157513))
      ^ ((uint64_t)clock() * UINT64_C(809712647));
    if (!x)
        x = 1;
    while (n-->0) {
        x ^= x >> 12;
        x ^= x << 25;
        x ^= x >> 27;
    }

    rng64_state = x;
    return x;
}

static inline double rng64_one(void)
{
    return (double)rng64() / 18446744073709551616.0;
}

static inline int64_t rng64_intrange(rng64_intrange_spec *spec)
{
    const uint64_t  limit = spec->limit;
    const int       shift = spec->shift;
    uint64_t        value;

    do {
        value = rng64() >> shift;
    } while (value > limit);

    return spec->base + value;
}

static inline void rng64_set_intrange(rng64_intrange_spec *spec,
                                  int64_t minimum,
                                  int64_t maximum)
{
    int64_t   base;
    uint64_t  limit;
    int       bits = 0;

    if (minimum <= maximum) {
        base  = minimum;
        limit = maximum - minimum;
    } else {
        base  = maximum;
        limit = minimum - maximum;
    }

    spec->base  = base;
    spec->limit = limit;

    while (limit >= 32768) {
        limit >>= 16;
        bits   += 16;
    }
    while (limit >= 8) {
        limit >>= 4;
        bits   += 4;
    }
    while (limit > 0) {
        limit >>= 1;
        bits   += 1;
    }

    spec->shift = 64 - bits;
}

#endif /* RNG64_H */

但是,要获得相当差的伪随机数,需要做很多工作:我认为这不值得。


我通常改用Xorshift64 *。它速度快,相当随机(请参阅我的this extended comment),并且非常容易实现。

基本上,您可以使用一个较小的头文件,例如 rng64.h

rng64_randomize()

在程序开始附近的某个地方,调用time()以根据当前时间(通过clock()的挂钟以及用于通过{{1}执行当前进程的CPU时间)生成状态。 }。初始状态会有所调整,以确保快速连续运行代码时不会出现类似的序列。您可以将rng64_state设置为除零以外的任何值,以生成特定的序列。 (零状态将仅生成零。)我建议使用

printf("Using %" PRIu64 " as the Xorshift64* random number seed.\n", rng64_randomize());

会在程序开始附近打印种子和所使用的伪随机数生成器算法。这允许某人重现测试(通过将rng64_state设置为该值而不是调用rng64_randomize(),或使用他们自己的等效代码重新实现测试)。重现性好。

虽然(uint64_t)time(NULL)不能保证按C标准运行,但确实可以在我所知道的所有当前广泛使用的C实现中使用。

如果要与其他伪随机数生成器进行比较,只需使用类似的头文件重新实现另一个,然后包括它即可。这样,您无需更改任何使用生成器的代码,只需更改生成器代码本身即可。

rng_one()返回0到1.0(含)之间的统一伪随机数。如果您希望上限是互斥的,请使用例如

static inline double rng64_one(void)
{
    double  r;
    do {
        r = (double)rng64() / 18446744073709551616.0;
    } while (r >= 1.0);
    return r;
}

,并且如果两个限制都互斥(因此它永远不会精确返回0.0或1.0),则改为while (r <= 0.0 || r >= 1.0);

这是上面的rng64.h的使用示例:

#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#include "rng64.h"

int main(int argc, char *argv[])
{
    rng64_intrange_spec  r;
    int   minval, maxval, count, i;
    char  dummy;

    if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s MIN MAX COUNT\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program outputs COUNT pseudorandom integers,\n");
        fprintf(stderr, "between MIN and MAX, inclusive.\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    if (sscanf(argv[1], " %d %c", &minval, &dummy) != 1) {
        fprintf(stderr, "%s: Invalid minimum.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (sscanf(argv[2], " %d %c", &maxval, &dummy) != 1 || maxval < minval) {
        fprintf(stderr, "%s: Invalid maximum.\n", argv[2]);
        return EXIT_FAILURE;
    }
    if (sscanf(argv[3], " %d %c", &count, &dummy) != 1 || count < 0) {
        fprintf(stderr, "%s: Invalid count.\n", argv[3]);
        return EXIT_FAILURE;
    }

    fprintf(stderr, "Generating %d pseudorandom integers in [%d, %d],\n", count, minval, maxval);
    fprintf(stderr, "using Xorshift64* with seed %" PRIu64 ".\n", rng64_randomize());
    fflush(stderr);

    rng64_set_intrange(&r, minval, maxval);

    for (i = 0; i < count; i++)
        printf("%d\n", (int)rng64_intrange(&r));

    return EXIT_SUCCESS;
}

指定最小值和最大值(整数)以及要输出的整数数作为命令行参数。