【问题标题】:How to generate a random int in C?如何在 C 中生成随机整数?
【发布时间】:2010-10-23 18:24:15
【问题描述】:

在 C 中是否有生成随机 int 数的函数?还是我必须使用第三方库?

【问题讨论】:

标签: c random


【解决方案1】:

注意:出于安全考虑,请勿使用rand()。如果您需要加密安全号码,请改用see this answer

#include <time.h>
#include <stdlib.h>

srand(time(NULL));   // Initialization, should only be called once.
int r = rand();      // Returns a pseudo-random integer between 0 and RAND_MAX.

在 Linux 上,您可能更喜欢使用 random and srandom

【讨论】:

  • +1 为简单起见,但强调 srand() 应该只被调用 一次 可能是个好主意。此外,在线程应用程序中,您可能希望确保每个线程都存储生成器的状态,并为每个线程播种一次生成器。
  • @trusktr,它很复杂。原因如下:time() 每秒只更改一次。如果您从time() 播种,每次调用rand(),那么您将在一秒钟内为每次调用获得相同的值。但更大的原因是 rand() 的属性和类似的函数在每次运行时只播种一次的用例中最为人所知,而不是在每次调用时。依赖未经测试或未经证实的属性的“随机性”会导致麻烦。
  • @trusktr 用于一个简单的线性同余生成器(通常是rand()),使用rand() 播种充其量根本没有效果,最坏的情况是会破坏生成器的已知质量。这是一个深刻的课题。从阅读Knuth Vol 2 第 3 章开始,了解随机数,这是对数学和陷阱的最佳介绍。
  • 通过强制转换避免编译器警告:srand((unsigned int)time(NULL));
  • 请记住,这仍然是查看 PRNG 的弱方法。就在去年,Linux 上的一种密码锁类型病毒犯了随时间播种的错误,这大大减少了搜索空间。您所要做的就是对感染发生的时间有一个不错的了解,然后尝试从那个时候开始的种子。上次我听说,随机性的最佳来源是 /dev/urandom,据推测,它是从硬件温度等混沌来源的混搭中产生的。但是,如果您真正想要的只是让您的程序在每次运行时采取不同的行动,那么上述解决方案就可以了。
【解决方案2】:

&lt;stdlib.h&gt; 中的 rand() 函数返回一个介于 0 和 RAND_MAX 之间的伪随机整数。您可以使用srand(unsigned int seed) 设置种子。

通常的做法是将% 运算符与rand() 结合使用以获得不同的范围(但请记住,这会在一定程度上影响一致性)。例如:

/* random int between 0 and 19 */
int r = rand() % 20;

如果您真的关心一致性,您可以这样做:

/* Returns an integer in the range [0, n).
 *
 * Uses rand(), and so is affected-by/affects the same seed.
 */
int randint(int n) {
  if ((n - 1) == RAND_MAX) {
    return rand();
  } else {
    // Supporting larger values for n would requires an even more
    // elaborate implementation that combines multiple calls to rand()
    assert (n <= RAND_MAX)

    // Chop off all of the values that would cause skew...
    int end = RAND_MAX / n; // truncate skew
    assert (end > 0);
    end *= n;

    // ... and ignore results from rand() that fall above that limit.
    // (Worst case the loop condition should succeed 50% of the time,
    // so we can expect to bail out of this loop pretty quickly.)
    int r;
    while ((r = rand()) >= end);

    return r % n;
  }
}

【讨论】:

  • 这是一种常见的做法 好吧,但不是正确的做法。见thisthis
  • @Lazer:这就是为什么我说“尽管请记住,这在一定程度上会影响统一性”。
  • @AbhimanyuAryan % 是模运算符。它给你一个整数除法的余数,所以x % n 总是会给你一个介于0n - 1 之间的数字(只要xn 都是正数)。如果您仍然感到困惑,请尝试编写一个程序,将 i 的计数从 0 到 100,并为您选择的小于 100 的一些 n 打印出 i % n
  • @necromancer 我继续添加了一个完美统一的解决方案。
  • @Lazer 您发布的第二个链接实际上仍然不完全统一。投射到双重和背部并没有帮助。您发布的第一个链接有一个完全统一的解决方案,尽管它会为小的上限循环一个 lot。我为这个答案添加了一个完全统一的解决方案,即使对于小的上限也不应该循环太多。
【解决方案3】:

如果您需要安全的随机字符或整数:

how to safely generate random numbers in various programming languages 中所述,您需要执行以下操作之一:

例如:

#include "sodium.h"

int foo()
{
    char myString[32];
    uint32_t myInt;

    if (sodium_init() < 0) {
        /* panic! the library couldn't be initialized, it is not safe to use */
        return 1; 
    }


    /* myString will be an array of 32 random bytes, not null-terminated */        
    randombytes_buf(myString, 32);

    /* myInt will be a random number between 0 and 9 */
    myInt = randombytes_uniform(10);
}

randombytes_uniform() 在密码学上是安全且无偏见的。

【讨论】:

  • 是否应该在调用 randombytes_buf 之前播种 libsodium RNG?
  • 在某个时候只需致电sodium_init()。不用担心 RNG,它使用内核的。
  • 注意:我批准了最近对 sodium_init() 的编辑,尽管它不一定是我示例的一部分,因为它是一个重要的细节。
  • 为什么不鼓励使用 OpenSSL 和其他用户态 PRNG? OpenSSL 的 RAND_bytes() 的文档说它是一个加密安全的 PRNG。
【解决方案4】:

让我们来看看这个。首先,我们使用srand() 函数为随机发生器播种。基本上,计算机可以根据输入到srand() 的数字生成随机数。如果给定相同的种子值,那么每次都会产生相同的随机数。

因此,我们必须为随机发生器播种一个始终在变化的值。为此,我们使用 time() 函数将当前时间的值提供给它。

现在,当我们调用rand()时,每次都会产生一个新的随机数。

    #include <stdio.h>

    int random_number(int min_num, int max_num);

    int main(void)
    {
        printf("Min : 1 Max : 40 %d\n", random_number(1,40));
        printf("Min : 100 Max : 1000 %d\n",random_number(100,1000));
        return 0;
    }

    int random_number(int min_num, int max_num)
    {
        int result = 0, low_num = 0, hi_num = 0;

        if (min_num < max_num)
        {
            low_num = min_num;
            hi_num = max_num + 1; // include max_num in output
        } else {
            low_num = max_num + 1; // include max_num in output
            hi_num = min_num;
        }

        srand(time(NULL));
        result = (rand() % (hi_num - low_num)) + low_num;
        return result;
    }

【讨论】:

  • 不错的代码,但调用 'srand(time(NULL));' 不是一个好主意。此方法在 for 循环中调用时产生相同的数字。
  • 建议的涉及代码的编辑经常被拒绝。 Someone made one here 带有评论“算法错误。可能产生比最大值更大的数字”。自己还没有评估过索赔。
  • @Martin Smith 问题:1) 应该是 else{ low_num=max_num; hi_num=min_num+1; 2) 在 hi_num - low_num &gt; INT_MAX 时失败。 3) 在极少数情况下省略值INT_MAX &gt; hi_num - low_num &gt; RAND_MAX
  • 如果在同一秒内多次调用此函数,像这样重新播种会导致该函数产生相同的数字。如果你真的想重新播种,那么每秒只播种一次。
  • 次要:hi_num = max_num + 1; 缺乏防止溢出的保护。
【解决方案5】:

如果您需要比stdlib 提供的质量更好的伪随机数,请查看Mersenne Twister。它也更快。示例实现很丰富,例如here

【讨论】:

  • +1:看起来很酷,但我只是在做一个猜谜游戏。如果我要在业务应用程序中使用随机数生成器,那么我肯定会使用它。
  • 不要使用 Mersenne Twister,使用 xoroshiro128+ 或 PCG 之类的好东西。 (Relevant link.)
【解决方案6】:

标准 C 函数是rand()。为纸牌发牌已经足够了,但它很糟糕。 rand() 的许多实现通过一个简短的数字列表循环,并且低位具有更短的循环。一些程序调用rand() 的方式很糟糕,计算一个好的种子传递给srand() 很困难。

在 C 中生成随机数的最佳方法是使用第三方库,如 OpenSSL。例如,

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/rand.h>

/* 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;
}

为什么有这么多代码? Java 和 Ruby 等其他语言具有随机整数或浮点数的函数。 OpenSSL 只提供随机字节,所以我尝试模仿 Java 或 Ruby 如何将它们转换为整数或浮点数。

对于整数,我们希望避免模偏差。假设我们从rand() % 10000 获得了一些随机的 4 位整数,但rand() 只能返回 0 到 32767(就像在 Microsoft Windows 中一样)。从 0 到 2767 的每个数字比从 2768 到 9999 的每个数字出现的频率更高。为了消除偏差,我们可以在值低于 2768 时重试rand(),因为从 2768 到 32767 的 30000 个值均匀映射到 10000 个值从 0 到 9999。

对于浮点数,我们需要 53 个随机位,因为 double 拥有 53 位精度(假设它是 IEEE 双精度)。如果我们使用超过 53 位,我们就会得到舍入偏差。一些程序员编写像 rand() / (double)RAND_MAX 这样的代码,但 rand() 可能只返回 31 位,或者在 Windows 中只返回 15 位。

OpenSSL 的 RAND_bytes() 种子本身,可能通过在 Linux 中读取 /dev/urandom 来实现。如果我们需要很多随机数,从/dev/urandom 读取它们会太慢,因为它们必须从内核中复制。允许 OpenSSL 从种子中生成更多随机数会更快。

更多关于随机数:

【讨论】:

  • 感谢您的扩展回答。请注意,在这个问题的 24 个当前答案中,你是唯一一个对float/double 有额外解释的人,所以我已经澄清了这个问题,坚持使用int 数字以避免出现问题太宽泛。还有其他专门针对 float/double 随机值的 C 问题,因此您可能需要重新发布您对 stackoverflow.com/questions/13408990/…等问题的回答的后半部分
【解决方案7】:

如果您的系统支持arc4random 系列函数,我建议使用这些函数而不是标准的rand 函数。

arc4random 家族包括:

uint32_t arc4random(void)
void arc4random_buf(void *buf, size_t bytes)
uint32_t arc4random_uniform(uint32_t limit)
void arc4random_stir(void)
void arc4random_addrandom(unsigned char *dat, int datlen)

arc4random 返回一个随机的 32 位无符号整数。

arc4random_buf 将随机内容放入其参数buf : void *。内容量由bytes : size_t参数决定。

arc4random_uniform 返回一个随机的 32 位无符号整数,它遵循以下规则:0 &lt;= arc4random_uniform(limit) &lt; limit,其中 limit 也是一个无符号 32 位整数。

arc4random_stir/dev/urandom 读取数据并将数据传递给arc4random_addrandom 以额外随机化其内部随机数池。

arc4random_addrandomarc4random_stir 用来根据传递给它的数据填充其内部随机数池。

如果你没有这些功能,但你在 Unix 上,那么你可以使用这个代码:

/* This is C, not C++ */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h> /* exit */
#include <stdio.h> /* printf */

int urandom_fd = -2;

void urandom_init() {
  urandom_fd = open("/dev/urandom", O_RDONLY);

  if (urandom_fd == -1) {
    int errsv = urandom_fd;
    printf("Error opening [/dev/urandom]: %i\n", errsv);
    exit(1);
  }
}

unsigned long urandom() {
  unsigned long buf_impl;
  unsigned long *buf = &buf_impl;

  if (urandom_fd == -2) {
    urandom_init();
  }

  /* Read sizeof(long) bytes (usually 8) into *buf, which points to buf_impl */
  read(urandom_fd, buf, sizeof(long));
  return buf_impl;
}

urandom_init 函数打开/dev/urandom 设备,并将文件描述符放入urandom_fd

urandom 函数与调用rand 基本相同,只是更安全,它返回一个long(易于更改)。

但是,/dev/urandom 可能会有点慢,因此建议您将其用作不同随机数生成器的种子。

如果您的系统没有/dev/urandom,但/dev/random 或类似文件,那么您只需在urandom_init 中更改传递给open 的路径即可。 urandom_initurandom 中使用的调用和 API 是(我相信)POSIX 兼容的,因此,应该可以在大多数(如果不是所有)POSIX 兼容系统上运行。

注意:如果没有足够的可用熵,来自/dev/urandom 的读取不会阻塞,因此在这种情况下生成的值可能在密码学上是不安全的。如果你担心这一点,那就使用/dev/random,如果熵不足,它总是会阻塞。

如果您在另一个系统(即 Windows)上,则使用 rand 或某些内部 Windows 特定平台相关的非可移植 API。

urandomrandarc4random 调用的包装函数:

#define RAND_IMPL /* urandom(see large code block) | rand | arc4random */

int myRandom(int bottom, int top){
    return (RAND_IMPL() % (top - bottom)) + bottom;
}

【讨论】:

    【解决方案8】:

    C 不存在 STL。您必须调用 rand,或者更好的是,random。这些在标准库头文件stdlib.h 中声明。 rand 是 POSIX,random 是 BSD 规范函数。

    randrandom 之间的区别在于 random 返回一个更有用的 32 位随机数,而 rand 通常返回一个 16 位数字。 BSD 联机帮助页显示rand 的低位是循环且可预测的,因此rand 对于小数字可能毫无用处。

    【讨论】:

    • @Neil - 由于到目前为止所有答案都提到了 STL,我怀疑这个问题是经过快速编辑以删除不必要的参考。
    • rand() 对小数字来说并非毫无用处 - 如果您真的需要,您可以将它们移出并仅使用更随机的高位。
    • @Chris,如果随机数的大小已知,则可以,但是如果随机数的所需大小在运行时发生变化(例如洗牌动态数组等),则很难工作围绕这样一个警告。
    • 我找不到任何随机函数here :-(
    • @kasia.b 在那个链接中,有extern int rand(void);extern void srand(unsigned int);
    【解决方案9】:

    查看ISAAC(间接、移位、累加、加和计数)。其分布均匀,平均循环长度为2^8295。

    【讨论】:

    • ISAAC 是一个有趣的 RNG,因为它的速度很快,但还没有受到密码学的重视。
    【解决方案10】:

    这是在您选择的两个数字之间获取随机数的好方法。

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
        #define randnum(min, max) \
            ((rand() % (int)(((max) + 1) - (min))) + (min))
    
    int main()
    {
        srand(time(NULL));
    
        printf("%d\n", randnum(1, 70));
    }
    

    第一次输出:39

    第二次输出:61

    第三次输出:65

    您可以将randnum 之后的值更改为您选择的任何数字,它会在这两个数字之间为您生成一个随机数。

    【讨论】:

      【解决方案11】:

      您想使用rand()。注意(非常重要):确保为 rand 函数设置种子。否则,您的随机数就不是真正随机的。这是非常非常非常重要的。值得庆幸的是,您通常可以使用系统滴答计时器和日期的某种组合来获得好种子。

      【讨论】:

      • 两点 a) 你的随机数不是“真正的”随机的,无论你如何播种生成器。并且 b) 在许多情况下让伪随机序列始终相同是非常方便的 - 例如,用于测试。
      • 如果你的号码是真正随机的非常重要,你不应该使用 rand() 函数。
      • 无论您是否设置种子,rand 的值都不是“真正”随机的。给定一个已知的种子,序列是可预测的。 “真正的”随机数生成是困难的。 rand 不涉及熵。
      • 他们当然会 - 生成器由库为您播种(可能为零,但这是一个有效的种子)。
      • 啊,但是已知算法/已知种子对于调试任何使用随机数的程序都是必不可少的。记录与模拟运行一起使用的种子并不罕见,以便可以重新创建它以进行更详细的分析。根本不调用 srand() 就相当于调用 srand(1)。
      【解决方案12】:

      FWIW,答案是肯定的,有一个名为randstdlib.h 函数;此功能主要针对速度和分布进行调整,而不是针对不可预测性。几乎所有各种语言和框架的内置随机函数都默认使用此函数。还有一些“加密”随机数生成器,它们的可预测性要低得多,但运行速度要慢得多。这些应在任何类型的安全相关应用程序中使用。

      【讨论】:

        【解决方案13】:

        希望这比仅使用 srand(time(NULL)) 更加随机。

        #include <time.h>
        #include <stdio.h>
        #include <stdlib.h>
        
        int main(int argc, char **argv)
        {
            srand((unsigned int)**main + (unsigned int)&argc + (unsigned int)time(NULL));
            srand(rand());
        
            for (int i = 0; i < 10; i++)
                printf("%d\n", rand());
        }
        

        【讨论】:

        • 添加 srand(rand()) 不会增加该程序在 1 秒内多次执行的序列的随机性。 time(NULL) 仍然会为它们中的每一个返回相同的值,第一个 rand() 将返回相同的 long,而对 srand() 的第二次调用将具有相同的值,导致仍然具有相同的随机序列。使用 argc 的地址可能会有所帮助,前提是要保证每次执行程序时该地址都不同,这并不总是正确的。
        【解决方案14】:

        生成 9 到 50 之间随机数的 C 程序

        #include <time.h>
        #include <stdlib.h>
        
        int main()
        {
            srand(time(NULL));
            int lowerLimit = 10, upperLimit = 50;
            int r =  lowerLimit + rand() % (upperLimit - lowerLimit);
            printf("%d", r);
        }
        

        一般我们可以生成一个介于lowerLimit和upperLimit-1之间的随机数

        即 lowerLimit 是包容性的,或者说 r ∈ [ lowerLimit, upperLimit )

        【讨论】:

        • @Pang 这就是我在 9 和 50 之间而不是 9 和 50 之间明确提到的。
        • 你的模运算引入了偏差。
        【解决方案15】:

        我在最近的应用程序中遇到了一个严重的伪随机数生成器问题:我通过 Python 脚本反复调用了我的 C 程序,并使用以下代码作为种子:

        srand(time(NULL))
        

        但是,因为:

        • rand 将生成相同的伪随机序列,在 srand 中给出相同的种子(参见man srand);
        • 如前所述,时间函数仅在秒之间发生变化:如果您的应用程序在同一秒内运行多次,time 每次都将返回相同的值。

        我的程序生成了相同的数字序列。 你可以做 3 件事来解决这个问题:

        1. 将时间输出与运行时更改的其他一些信息混合在一起(在我的应用程序中,输出名称):

          srand(time(NULL) | getHashOfString(outputName))
          

          我使用djb2 作为我的哈希函数。

        2. 提高时间分辨率。在我的平台上,clock_gettime 可用,所以我使用它:

          #include<time.h>
          struct timespec nanos;
          clock_gettime(CLOCK_MONOTONIC, &nanos)
          srand(nanos.tv_nsec);
          
        3. 同时使用这两种方法:

          #include<time.h>
          struct timespec nanos;
          clock_gettime(CLOCK_MONOTONIC, &nanos)
          srand(nanos.tv_nsec | getHashOfString(outputName));
          

        选项 3 确保(据我所知)最佳种子随机性,但它可能仅在非常快速的应用程序中产生差异。 在我看来,选项 2 是一个安全的选择。

        【讨论】:

        • 即使使用这些启发式方法,也不要依赖 rand() 来获取加密数据。
        • rand() 不应该用于加密数据,我同意。至少对我来说,我的应用程序不涉及加密数据,所以对我来说,给定的方法是可以的。
        【解决方案16】:

        嗯,STL 是 C++,而不是 C,所以我不知道你想要什么。但是,如果您想要 C,则可以使用 rand()srand() 函数:

        int rand(void);
        
        void srand(unsigned seed);
        

        这些都是 ANSI C 的一部分。还有 random() 函数:

        long random(void);
        

        但据我所知,random() 不是标准的 ANSI C。第三方库可能不是一个坏主意,但这完全取决于您真正需要生成的数字的随机程度。

        【讨论】:

          【解决方案17】:

          rand()是最方便的随机数生成方式。

          您还可以从任何在线服务(如 random.org)获取随机数。

          【讨论】:

          • 你也可以从任何在线服务(如 random.org)中捕获随机数如果你在 C 语言中包含一种便携、高效的方法来执行此操作。
          【解决方案18】:
          #include <stdio.h>
          #include <stdlib.h>
          
          void main() 
          {
              int visited[100];
              int randValue, a, b, vindex = 0;
          
              randValue = (rand() % 100) + 1;
          
              while (vindex < 100) {
                  for (b = 0; b < vindex; b++) {
                      if (visited[b] == randValue) {
                          randValue = (rand() % 100) + 1;
                          b = 0;
                      }
                  }
          
                  visited[vindex++] = randValue;
              }
          
              for (a = 0; a < 100; a++)
                  printf("%d ", visited[a]);
          }
          

          【讨论】:

          • 注意:理论上这个函数有可能无限期挂起,这取决于系统对rand的实现。
          【解决方案19】:

          在现代 x86_64 CPU 上,您可以通过 _rdrand64_step() 使用硬件随机数生成器

          示例代码:

          #include <immintrin.h>
          
          uint64_t randVal;
          if(!_rdrand64_step(&randVal)) {
            // Report an error here: random number generation has failed!
          }
          // If no error occured, randVal contains a random 64-bit number
          

          【讨论】:

            【解决方案20】:
            #include <stdio.h>
            #include <dos.h>
            
            int random(int range);
            
            int main(void)
            {
                printf("%d", random(10));
                return 0;
            }
            
            int random(int range)
            {
                struct time t;
                int r;
            
                gettime(&t);
                r = t.ti_sec % range;
                return r;
            }
            

            【讨论】:

              【解决方案21】:

              尽管这里有很多人建议rand(),但你不想使用rand(),除非你必须这样做! rand() 产生的随机数通常非常糟糕。引用 Linux 手册页:

              Linux C 库中的rand()srand() 版本使用与random(3)srandom(3) 相同的随机数生成器,因此低位应与高位一样随机。然而,在较旧的 rand() 实现中,以及在不同系统上的当前实现中,低阶位的随机性远低于高阶位。当需要良好的随机性时,请勿在旨在可移植的应用程序中使用此功能。 (请改用random(3)

              关于可移植性,random() 也由 POSIX 标准定义了相当长一段时间。 rand() 较旧,它已经出现在第一个 POSIX.1 规范(IEEE Std 1003.1-1988)中,而 random() 第一次出现在 POSIX.1-2001(IEEE Std 1003.1-2001)中,但当前的 POSIX 标准是已经是 POSIX.1-2008(IEEE Std 1003.1-2008),一年前才收到更新(IEEE Std 1003.1-2008,2016 版)。所以我认为random() 非常便携。

              POSIX.1-2001还引入了lrand48()mrand48()函数,see here

              该系列函数应使用线性同余算法和 48 位整数算法生成伪随机数。

              相当不错的伪随机源是arc4random() 函数,可在许多系统上使用。不属于任何官方标准,1997 年左右出现在 BSD 中,但您可以在 Linux 和 macOS/iOS 等系统上找到它。

              【讨论】:

              • random() 在 Windows 上不存在。
              • @BjörnLindqvist Windows 也不是 POSIX 系统;它几乎是市场上唯一至少不支持基本 POSIX API 的系统(甚至像 iOS 这样的锁定系统也支持)。 Windows 仅支持rand(),因为它也是 C 标准所要求的。对于其他任何事情,您只需要一个适用于 Windows 的特殊解决方案,就像往常一样。 #ifdef _WIN32 是您在想要支持 Windows 的跨平台代码中最常看到的短语,并且通常有一种解决方案适用于所有系统,而另一种解决方案仅适用于 Windows。
              【解决方案22】:
              #include<stdio.h>
              #include<stdlib.h>
              #include<time.h>
              
              //generate number in range [min,max)
              int random(int min, int max){
                  int number = min + rand() % (max - min);
                  return number; 
              }
              
              //Driver code
              int main(){
                  srand(time(NULL));
                  for(int i = 1; i <= 10; i++){
                      printf("%d\t", random(10, 100));
                  }
                  return 0;
              }
              

              【讨论】:

                【解决方案23】:

                听到一个很好的解释为什么使用rand() 在给定范围内产生均匀分布的随机数是一个坏主意,我决定看看输出的实际偏差程度。我的测试用例是公平的掷骰子。这是 C 代码:

                #include <stdio.h>
                #include <stdlib.h>
                #include <time.h>
                
                int main(int argc, char *argv[])
                {
                    int i;
                    int dice[6];
                
                    for (i = 0; i < 6; i++) 
                      dice[i] = 0;
                    srand(time(NULL));
                
                    const int TOTAL = 10000000;
                    for (i = 0; i < TOTAL; i++)
                      dice[(rand() % 6)] += 1;
                
                    double pers = 0.0, tpers = 0.0;
                    for (i = 0; i < 6; i++) {
                      pers = (dice[i] * 100.0) / TOTAL;
                      printf("\t%1d  %5.2f%%\n", dice[i], pers);
                      tpers += pers;
                    }
                    printf("\ttotal:  %6.2f%%\n", tpers);
                }
                

                这是它的输出:

                 $ gcc -o t3 t3.c
                 $ ./t3 
                        1666598  16.67%     
                        1668630  16.69%
                        1667682  16.68%
                        1666049  16.66%
                        1665948  16.66%
                        1665093  16.65%
                        total:  100.00%
                 $ ./t3     
                        1667634  16.68%
                        1665914  16.66%
                        1665542  16.66%
                        1667828  16.68%
                        1663649  16.64%
                        1669433  16.69%
                        total:  100.00%
                

                我不知道你需要你的随机数有多统一,但以上看起来足够统一,可以满足大多数需求。

                编辑:最好用比 time(NULL) 更好的东西初始化 PRNG。

                【讨论】:

                • rand() 可能无法通过其他随机性测试,例如 diehard tests。 rand() 因平台而异;来自 GNU/Linux 的 rand() 值可能比来自 BSD 或 Windows 的值更好。
                • 这不是测试随机性的有效方法。
                • 取决于目的和威胁/风险模型。对于加密强的 RNG - 当然,请使用 RDRAND(或 RDSEED)。对于一个简单的掷骰子(不是赌场级别的)恕我直言,以上就足够了。关键字是“好足够”。
                【解决方案24】:

                对于 Linux C 应用程序:

                这是我从上面的答案中修改的代码,它遵循我的 C 代码实践并返回任意大小的随机缓冲区(具有正确的返回代码等)。确保在程序开始时调用一次urandom_open()

                int gUrandomFd = -1;
                
                int urandom_open(void)
                {
                    if (gUrandomFd == -1) {
                        gUrandomFd = open("/dev/urandom", O_RDONLY);
                    }
                
                    if (gUrandomFd == -1) {
                        fprintf(stderr, "Error opening /dev/urandom: errno [%d], strerrer [%s]\n",
                                  errno, strerror(errno));
                        return -1;
                    } else {
                        return 0;
                    }
                }
                
                
                void urandom_close(void)
                {
                    close(gUrandomFd);
                    gUrandomFd = -1;
                }
                
                
                //
                // This link essentially validates the merits of /dev/urandom:
                // http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
                //
                int getRandomBuffer(uint8_t *buf, int size)
                {
                    int ret = 0; // Return value
                
                    if (gUrandomFd == -1) {
                        fprintf(stderr, "Urandom (/dev/urandom) file not open\n");
                        return -1;
                    }
                
                    ret = read(gUrandomFd, buf, size);
                
                    if (ret != size) {
                        fprintf(stderr, "Only read [%d] bytes, expected [%d]\n",
                                 ret, size);
                        return -1;
                    } else {
                        return 0;
                    }
                }
                

                【讨论】:

                  【解决方案25】:

                  这是我的方法(rand() 的包装):

                  我还可以缩放以允许 min 为 INT_MIN 且 max 为 INT_MAX 的情况,单独使用 rand() 通常是不可能的,因为它返回的值从 0RAND_MAX,包括 (1/ 2 那个范围)。

                  像这样使用它:

                  const int MIN = 1;
                  const int MAX = 1024;
                  // Get a pseudo-random number between MIN and MAX, **inclusive**.
                  // Seeding of the pseudo-random number generator automatically occurs
                  // the very first time you call it.
                  int random_num = utils_rand(MIN, MAX);
                  

                  定义和doxygen描述:

                  #include <assert.h>
                  #include <stdbool.h>
                  #include <stdlib.h>
                  
                  /// \brief      Use linear interpolation to rescale, or "map" value `val` from range
                  ///             `in_min` to `in_max`, inclusive, to range `out_min` to `out_max`, inclusive.
                  /// \details    Similar to Arduino's ingenious `map()` function:
                  ///             https://www.arduino.cc/reference/en/language/functions/math/map/
                  ///
                  /// TODO(gabriel): turn this into a gcc statement expression instead to prevent the potential for
                  /// the "double evaluation" bug. See `MIN()` and `MAX()` above.
                  #define UTILS_MAP(val, in_min, in_max, out_min, out_max) \
                      (((val) - (in_min)) * ((out_max) - (out_min)) / ((in_max) - (in_min)) + (out_min))
                  
                  /// \brief      Obtain a pseudo-random integer value between `min` and `max`, **inclusive**.
                  /// \details    1. If `(max - min + 1) > RAND_MAX`, then the range of values returned will be
                  ///             **scaled** to the range `max - min + 1`, and centered over the center of the
                  ///             range at `(min + max)/2`. Scaling the numbers means that in the case of scaling,
                  ///             not all numbers can even be reached. However, you will still be assured to have
                  ///             a random distribution of numbers across the full range.
                  ///             2. Also, the first time per program run that you call this function, it will
                  ///             automatically seed the pseudo-random number generator with your system's
                  ///             current time in seconds.
                  /// \param[in]  min         The minimum pseudo-random number you'd like, inclusive. Can be positive
                  ///                         OR negative.
                  /// \param[in]  max         The maximum pseudo-random number you'd like, inclusive. Can be positive
                  ///                         OR negative.
                  /// \return     A pseudo-random integer value between `min` and `max`, **inclusive**.
                  int utils_rand(int min, int max)
                  {
                      static bool first_run = true;
                      if (first_run)
                      {
                          // seed the pseudo-random number generator with the seconds time the very first run
                          time_t time_now_sec = time(NULL);
                          srand(time_now_sec);
                          first_run = false;
                      }
                  
                      int range = max - min + 1;
                      int random_num = rand();  // random num from 0 to RAND_MAX, inclusive
                  
                      if (range > RAND_MAX)
                      {
                          static_assert(
                              sizeof(long int) > sizeof(int),
                              "This must be true or else the below mapping/scaling may have undefined overflow "
                              "and not work properly. In such a case, try casting to `long long int` instead of "
                              "just `long int`, and update this static_assert accordingly.");
                  
                          random_num = UTILS_MAP((long int)random_num, (long int)0, (long int)RAND_MAX, (long int)min,
                                                 (long int)max);
                          return random_num;
                      }
                  
                      // This is presumably a faster approach than the map/scaling function above, so do this faster
                      // approach below whenever you don't **have** to do the more-complicated approach above.
                      random_num %= range;
                      random_num += min;
                  
                      return random_num;
                  }
                  
                  

                  另见:

                  1. [我在上面写下我的答案后发现了这个问答,但它显然非常相关,而且他们对非缩放范围的情况做了同样的事情]How do I get a specific range of numbers from rand()?
                  2. [我还需要进一步研究和阅读这个答案——似乎有一些关于通过不单独使用模数来保持良好随机性的好处]How do I get a specific range of numbers from rand()?
                    1. http://c-faq.com/lib/randrange.html

                  【讨论】:

                    【解决方案26】:

                    如果您需要 128 个安全随机位,则符合 RFC 1750 的解决方案是读取已知会生成可用熵位的硬件源(例如旋转磁盘)。更好的是,好的实现应该使用混合函数组合多个来源,最后通过重新映射或删除输出去偏它们的输出分布。

                    如果您需要比这更多的位,则符合要求的做法是从 128 个安全随机位的序列开始,并将其拉伸到所需的长度,将其映射到人类可读的文本等。

                    如果你想在 C 语言中生成一个安全的随机数,我会按照这里的源代码:

                    https://wiki.sei.cmu.edu/confluence/display/c/MSC30-C.+Do+not+use+the+rand%28%29+function+for+generating+pseudorandom+numbers

                    请注意,Windows 使用的是 BCryptGenRandom,而不是 CryptGenRandom,后者在过去二十年中变得不安全。您可以自己确认 BCryptGenRandom 符合 RFC 1750。

                    对于 POSIX 兼容的操作系统,例如Ubuntu(Linux 的一种风格),您可以简单地从/dev/urandom/dev/random 中读取,这是一个类似于文件的设备接口,通过以符合 RFC 1750 的方式组合多个源来生成熵位。您可以使用readfread 从这些“文件”中读取所需数量的字节,就像读取任何其他文件一样,但请注意,从/dev/random 读取将阻塞,直到有足够的新熵位可用,而/dev/urandom 不会,这可能是一个安全问题。你可以通过检查可用熵池的大小来解决这个问题,或者我从entropy_avail 读取,或者使用ioctl

                    【讨论】:

                      【解决方案27】:

                      我的简约解决方案应该适用于[min, max) 范围内的随机数。在调用函数之前使用srand(time(NULL))

                      int range_rand(int min_num, int max_num) {
                          if (min_num >= max_num) {
                              fprintf(stderr, "min_num is greater or equal than max_num!\n"); 
                          }
                          return min_num + (rand() % (max_num - min_num));
                      } 
                      

                      【讨论】:

                        【解决方案28】:

                        您可以使用悬空指针的概念。

                        指向已被删除(或释放)的内存位置的指针称为悬空指针。

                        打印时会显示随机值。

                        【讨论】:

                        • 这是不使用任何内置函数如rand()
                        • 这样做是undefined behavior,可能会导致您的程序崩溃。
                        • 仅仅因为它对你有用并不意味着它对每个人都有效。这就是未定义行为如何表现出来的一部分。
                        【解决方案29】:

                        试试这个,我把上面已经提到的一些概念放在一起:

                        /*    
                        Uses the srand() function to seed the random number generator based on time value,
                        then returns an integer in the range 1 to max. Call this with random(n) where n is an integer, and you get an integer as a return value.
                         */
                        
                        int random(int max) {
                            srand((unsigned) time(NULL));
                            return (rand() % max) + 1;
                        }
                        

                        【讨论】:

                        • 这段代码不好。每次你想打电话给rand() 时都打电话给srand() 是一个糟糕的主意。由于time() 通常会在 内返回一个值,因此快速调用此函数将返回相同的“随机”值。
                        • 这个函数会和Unix的random()函数混淆。
                        猜你喜欢
                        • 1970-01-01
                        • 2020-08-06
                        • 1970-01-01
                        • 2012-11-06
                        • 1970-01-01
                        • 2011-12-08
                        相关资源
                        最近更新 更多