是否有函数在C中生成随机整数?还是我必须使用第三方库?
srand
:为什么只调用一次。
是否有函数在C中生成随机整数?还是我必须使用第三方库?
srand
:为什么只调用一次。
Answers:
注意:请勿
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和srandom。
time()
每秒仅更改一次。如果您从播种time()
,对于的每次调用rand()
,那么您在一秒钟内的每次调用都会获得相同的值。但是更大的原因是,rand()
对于这种用例来说,的属性和类似功能最适合用例,因为它们每次运行时仅被准确地植入一次,而不是在每个调用中都被植入。根据未经测试或未经验证的属性的“随机性”会导致麻烦。
rand()
通常是这种情况)的@trusktr rand()
充其量根本没有作用,最坏的情况是会破坏该生成器的已知质量。这是一个很深的主题。首先阅读有关随机数的Knuth Vol 2第3章,作为对数学和陷阱的最佳介绍。
srand((unsigned int)time(NULL));
中的rand()
函数<stdlib.h>
返回介于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;
}
}
%
是模运算符。它为您提供整数除法的余数,因此x % n
将始终为您提供介于0
和之间的数字n - 1
(只要x
和n
均为正数)。如果你仍然觉得迷惑,试着写一个程序,已经i
数从0到100,并打印出i % n
一些n
你选择的小于100
如如何安全地以各种编程语言生成随机数所述,您需要执行以下操作之一:
randombytes
API/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()
是加密安全且无偏见的。
sodium_init()
在某个时候打电话。不用担心RNG,它使用内核的。
sodium_init()
尽管它不一定是我的示例的一部分,因为它是重要的细节。
让我们经历一下。首先,我们使用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;
}
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;
缺乏防溢出保护。
如果您需要比stdlib
提供的质量更好的伪随机数,请查看Mersenne Twister。它也更快。示例实现很多,例如在这里。
标准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如何将其转换为整数或浮点数。
对于整数,我们要避免模偏差。假设我们从中获得了一些随机的4位整数rand() % 10000
,但是rand()
只能返回0到32767(就像在Microsoft Windows中一样)。从0到2767的每个数字比在从2768到9999的每个数字出现的频率更高。要消除偏差,我们可以rand()
在值小于2768时重试,因为从2768到32767的30000值均匀映射到从0到2767 的10000个值。 9999。
对于浮点数,我们需要53个随机位,因为a double
拥有53位的精度(假设它是IEEE的两倍)。如果我们使用超过53位,则会得到舍入偏差。一些程序员编写类似的代码rand() / (double)RAND_MAX
,但rand()
可能只返回31位,或者在Windows中仅返回15位。
OpenSSL的RAND_bytes()
种子本身,也许是/dev/urandom
在Linux中阅读的。如果我们需要许多随机数,那么从中读取所有随机数将太慢/dev/urandom
,因为必须从内核复制它们。允许OpenSSL从种子生成更多随机数的速度更快。
有关随机数的更多信息:
srand()
。如果无法读取,它将混合来自当前时间的位,进程ID和一些指针/dev/urandom
。float
/的答案double
,因此我已澄清了该问题,请坚持使用int
数字,以免使其过于宽泛。有与专门处理其他C问题float
/ double
随机值,所以你可能需要将你的答案下半年转贴到这样的问题stackoverflow.com/questions/13408990/...
如果您的系统支持该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_addrandom
用于arc4random_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 4 bytes, or 32 bits 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
或类似的文件,那么你可以简单地改变传递到路径open
在urandom_init
。(我认为)POSIX中使用的urandom_init
和urandom
它们的调用和API 符合POSIX,因此,即使不是所有POSIX兼容系统,它们也应该可以在大多数系统上工作。
注意:/dev/urandom
如果没有足够的熵,则不会阻止读操作,因此在这种情况下生成的值可能在密码上不安全。如果您对此感到担心,请使用/dev/random
,如果熵不足,它将始终阻止。
如果您在另一个系统(即Windows)上,请使用rand
或某些内部Windows特定于平台的非便携式API。
包装功能urandom
,rand
或arc4random
来电:
#define RAND_IMPL /* urandom(see large code block) | rand | arc4random */
int myRandom(int bottom, int top){
return (RAND_IMPL() % (top - bottom)) + bottom;
}
C不存在STL。您必须致电rand
,或者更好random
。这些在标准库头中声明stdlib.h
。rand
是POSIX,random
是BSD规范功能。
rand
和之间的区别在于,random
它random
返回更多可用的32位随机数,并且rand
通常返回16位数字。BSD手册页显示的低位rand
是循环且可预测的,因此rand
对于较小的数字可能没有用。
extern int rand(void);
和extern void srand(unsigned int);
。
看一下ISAAC(间接,移位,累加,添加和计数)。它的分布均匀,平均周期为2 ^ 8295。
这是在选择的两个数字之间获取随机数的好方法。
#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
您选择的任何数字,这将为您在这两个数字之间生成一个随机数。
您要使用rand()
。注意(非常重要):确保为rand函数设置种子。如果不这样做,那么您的随机数并不是真正的随机数。这非常非常重要。幸运的是,您通常可以结合使用系统滴答计时器和日期来获得良好的种子。
希望这比使用更加随机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());
}
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)
rand()
是生成随机数最方便的方法。
您也可以从任何在线服务(例如random.org)中获取随机数。
#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]);
}
在现代的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
#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;
}
听到一个很好的解释,为什么用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%
我不知道您需要多少随机数,但是上面的代码足以满足大多数需求。
编辑:用比更好的方法初始化PRNG是个好主意time(NULL)
。
我在最近的应用程序中遇到了一个伪随机数生成器的严重问题:我通过pyhton脚本反复调用了我的C程序,并且使用以下代码作为种子:
srand(time(NULL))
但是,由于:
man srand
);time
则每次将返回相同的值。我的程序生成相同的数字序列。您可以做三件事来解决这个问题:
将时间输出与运行中其他一些信息更改(在我的应用程序中,输出名称):
srand(time(NULL) | getHashOfString(outputName))
我使用djb2作为我的哈希函数。
增加时间分辨率。在我的平台clock_gettime
上可用,因此我使用它:
#include<time.h>
struct timespec nanos;
clock_gettime(CLOCK_MONOTONIC, &nanos)
srand(nanos.tv_nsec);
一起使用两种方法:
#include<time.h>
struct timespec nanos;
clock_gettime(CLOCK_MONOTONIC, &nanos)
srand(nanos.tv_nsec | getHashOfString(outputName));
选项3(据我所知)可确保您获得最佳的种子随机性,但它可能仅在非常快速的应用程序上会产生差异。我认为选项2是一个不错的选择。
rand()
我同意,不应将其用于加密数据。至少对我来说,我的应用程序不涉及加密数据,因此对我来说,使用给定的方法是可以的。
尽管rand()
这里有很多人的建议,但rand()
除非有必要,否则您还是不想使用!rand()
产生的随机数通常非常糟糕。引用Linux手册页:
的版本
rand()
和srand()
Linux的C库中使用相同的随机数发生器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()
功能,请参见此处:
该函数系列应使用线性同余算法和48位整数算术生成伪随机数。
相当不错的伪随机源是 arc4random()
许多系统上可用功能。它不是任何正式标准的一部分,它于1997年左右出现在BSD中,但是您可以在Linux和macOS / iOS等系统上找到它。
random()
在Windows上不存在。
rand()
因为C标准也需要它。像往常一样,对于其他任何事情,您仅需要Windows的特殊解决方案即可。#ifdef _WIN32
在跨平台代码中,该短语是您最常支持Windows的短语,通常,有一种解决方案适用于所有系统,而只有Windows才需要。
对于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;
}
}
我的简约解决方案应该适用于范围内的随机数[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));
}
尝试一下,我将其与上面已经提到的一些概念放在一起:
/*
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;
}
srand()
要调用每次rand()
是一个可怕的想法。由于time()
通常以秒为单位返回一个值,因此快速调用此函数将返回相同的“随机”值。
random()
函数混淆。