ChatGPT解决这个技术问题 Extra ChatGPT

如何在 C 中生成随机整数?

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


N
Neuron

注意:不要使用 rand() 来保证安全。如果您需要加密安全号码,请参阅此答案。

#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,据推测,它是从硬件温度等混沌来源的混搭中产生的。但是,如果您真正想要的只是让您的程序在每次运行时采取不同的行动,那么上述解决方案就可以了。
L
Laurence Gonsalves

<stdlib.h> 中的 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 您发布的第二个链接实际上仍然不是完全统一的。投射到双重和背部并没有帮助。您发布的第一个链接有一个完全统一的解决方案,尽管它会为小的上限循环很多。我为这个答案添加了一个完全统一的解决方案,即使对于小的上限也不应该循环太多。
A
Andrew

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

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

使用 libsodium 的 randombytes API

非常小心地从 libsodium 的 sysrandom 实现中重新实现你需要的东西

更广泛地说,使用 /dev/urandom,而不是 /dev/random。不是 OpenSSL(或其他用户空间 PRNG)。

例如:

#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。
R
Robert Christopher

让我们来看看这个。首先,我们使用 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 > INT_MAX 时失败。 3) 在罕见情况 INT_MAX > hi_num - low_num > RAND_MAX 中省略值。
如果在同一秒内多次调用此函数,像这样重新播种将导致该函数产生相同的数字。如果你真的想重新播种,那么每秒只重新播种一次。
次要:hi_num = max_num + 1; 缺乏防止溢出的保护。
M
MH114

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


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

标准 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 从种子中生成更多随机数会更快。

更多关于随机数:

Perl 的 Perl_seed() 是如何在 C 中为 srand() 计算种子的示例。如果它无法读取 /dev/urandom,它会混合来自当前时间的位、进程 ID 和一些指针。

OpenBSD 的 arc4random_uniform() 解释了模偏差。

java.util.Random 的 Java API 描述了从随机整数中消除偏差并将 53 位打包成随机浮点数的算法。


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

如果您的系统支持 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 <= arc4random_uniform(limit) < limit,其中 limit 也是一个无符号 32 位整数。

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

arc4random_stir 使用 arc4random_addrandom 根据传递给它的数据填充其内部随机数池。

如果您没有这些功能,但您使用的是 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;
}

M
MD XF

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);
g
geofftnz

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


ISAAC 是一个有趣的 RNG,因为它的速度很快,但还没有受到密码学的重视。
M
MD XF

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

#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 之后的值更改为您选择的任何数字,它会在这两个数字之间为您生成一个随机数。


C
Chris Lutz

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

int rand(void);

void srand(unsigned seed);

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

long random(void);

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


M
MD XF

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


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

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


M
MD XF

希望这比仅使用 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());
}

如果该程序在 1 秒内多次执行,添加 srand(rand()) 不会增加序列的随机性。 time(NULL) 仍然会为它们中的每一个返回相同的值,第一个 rand() 将返回相同的 long,第二次调用 srand() 将具有相同的值,导致仍然具有相同的随机序列。使用 argc 的地址可能会有所帮助,前提是要保证每次执行程序时该地址都不同,但这并不总是正确的。
S
Shivam K. Thakkar

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

#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 之间明确提到的。
您的模运算引入了偏差。
Y
Yun

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

srand(time(NULL))

但是,由于:

rand 将生成相同的伪随机序列,在 srand 中给出相同的种子(参见 man srand);

如前所述,时间函数仅在秒之间发生变化:如果您的应用程序在同一秒内多次运行,则时间每次都将返回相同的值。

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

将时间输出与运行时更改的其他一些信息混合在一起(在我的应用程序中,输出名称): srand(time(NULL) | getHashOfString(outputName)) 我使用 djb2 作为我的哈希函数。提高时间分辨率。在我的平台上,clock_gettime 可用,所以我使用它:#include struct timespec nanos; clock_gettime(CLOCK_MONOTONIC, &nanos) srand(nanos.tv_nsec);同时使用这两种方法:#include struct timespec nanos; clock_gettime(CLOCK_MONOTONIC, &nanos) srand(nanos.tv_nsec | getHashOfString(outputName));

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


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

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

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


如果您在 C 中包含一种可移植的、有效的方法来执行此操作,您还可以从任何在线服务(如 random.org Bounty)中捕获随机数。
M
MD XF
#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 的实现。
S
Serge Rogatch

在现代 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

这应该在重试循环中使用,而不是 if。如果多个线程快速提取随机数,则实际 CPU 上会出现临时故障。有关更好的包装函数,请参阅 RDRAND and RDSEED intrinsics on various compilers?
M
Mecki

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

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

关于可移植性,POSIX 标准也定义了 random() 很长一段时间。 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。
M
MD XF
#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;
}

d
duyuanchao
#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;
}

M
Mouse

听到一个很好的解释为什么使用 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)。对于一个简单的掷骰子(不是赌场级别的)恕我直言,以上就足够了。关键词是“足够好”。
%6 表示您的随机性包括较高的位,而不仅仅是像 %8 那样的低位。因此,避免了基于 LCG 的 rand() 的一大缺点。当然,LCG 的大问题不是长期的整体分布,而是像低位每次从奇数到偶数交替,对于一个非常简单的 LCG。将计数器从 0 增加到 n 也会提供非常均匀的分布,但不是随机的。所以你的测试没有区分随机序列和近乎线性的序列,因此不能告诉我们很多类型的潜在问题,只有偏差。
B
Brad Grissom

对于 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;
    }
}

G
Gabriel Staples

这是我的方法(围绕 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;
}

也可以看看:

[我在上面写下我的答案后发现了这个问答,但它显然非常相关,并且他们对非缩放范围的情况做同样的事情]如何从 rand() 获得特定范围的数字? [我还需要学习和阅读这个答案——似乎有一些关于通过不单独使用模数来保持良好随机性的好处] 如何从 rand() 获得特定范围的数字? http://c-faq.com/lib/randrange.html


a
angstyloop

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

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

如果您想在 CI 中生成一个安全的随机数,请遵循此处的源代码:

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)来解决这个问题。


H
Han

与此相关的 glibc 特定函数(应在大多数 Linux 环境中找到)是 random(),或者您可能对它的线程安全版本 random_r() 感兴趣。在将 struct random_data 传递给 random_r() 之前,您必须使用 initstate_r() 对其进行初始化。

这是快速代码示例:

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

void xxx (void) {
    unsigned int seed = (unsigned int) time(NULL);
    char rnd_state[17] = {0};
    struct random_data rnd_st_buf = {0};
    initstate_r(seed, &rnd_state[0], 17, &rnd_st_buf);
    for(size_t idx = 0; idx < 8; idx++) {
        int32_t rnd_int = 0;
        char rnd_seq_str[6] = {0};
        random_r(&rnd_st_buf, &rnd_int);
        memcpy((char *)&rnd_seq_str[0], (char *)&rnd_int, 4);
        printf("random number : 0x%08x,  \n", rnd_int);
    }
}

佚名

您可以生成随机字符,然后将它们视为 int :

#include <stdlib.h>
#include <stdio.h>

typedef double rand_type; // change double to int

rand_type my_rand() {
    char buff[sizeof(rand_type)];
    for (size_t i = 0 ; i < sizeof(rand_type) ; ++i)
        buff[i] = (char) rand();
    return *(rand_type *) buff;
}

int main() {
    int i ; // srand as you want
    for (i = 0 ; i < 10 ; ++i)
        printf("%g\n", my_rand()); // change %g to %d
    return 0 ;
}

我看不出这个答案有什么问题,所以我投了赞成票。我很欣赏 RNG 应该使用时间或其他一些输入设备来播种,但有时你希望某些东西显得随机和混乱,但你实际上可能希望随机性是可预测的和确定性的 - 例如重现错误。所以我认为这个答案没有任何问题。
m
mgthomas99

我的简约解决方案应该适用于 [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));
} 

c
confused_

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

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

它会在打印时显示随机值。


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