如何在C中生成一个随机数?
有一个函数可以在C中生成一个随机数吗? 或者我将不得不使用第三方库?
注意 :不要使用
rand()
来提高安全性。 如果您需要密码安全的号码, 请参阅此答案 。
#include <time.h> #include <stdlib.h> srand(time(NULL)); // should only be called once int r = rand(); // returns a pseudo-random integer between 0 and RAND_MAX
<stdlib.h>
的rand()
函数返回0到RAND_MAX
之间的伪随机整数。 你可以使用srand(unsigned int seed)
来设置种子。
将rand()
运算符与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 { // Chop off all of the values that would cause skew... long end = RAND_MAX / n; // truncate skew assert (end > 0L); 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; } }
如果你需要安全的随机字符或整数:
就如何以各种编程语言安全地生成随机数而言 ,您将需要执行以下操作之一:
- 使用libsodium的
randombytes
API - 重新执行你自己需要的libsodium的sysrandom实现 ,非常仔细
- 更广泛地说, 使用
/dev/urandom
,而不是/dev/random
。 不是OpenSSL(或其他用户空间PRNG)。
例如:
#include "sodium.h" int foo() { char myString[32]; uint32_t myInt; /* 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()
是密码安全且无偏见的。
如果你需要比stdlib
提供的更好质量的伪随机数字,请查看Mersenne Twister 。 它也更快。 示例实现很丰富,例如这里 。
让我们通过这个。 首先我们使用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; }
标准的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(uc, sizeof(uc))) { fprintf(stderr, "Can't get random bytes!\n"); exit(1); } } while (ui < (-limit % limit)); /* ui < (2**size % limit) */ return ui % 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(uc, sizeof(uc))) { fprintf(stderr, "Can't get random bytes!\n"); exit(1); } /* 53 bits / 2**53 */ return (ui >> 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的double)。 如果我们使用超过53位,我们得到四舍五入的偏见。 一些程序员编写像rand() / (double)RAND_MAX
这样的代码,但是rand()
可能只返回31位,或者在Windows中只返回15位。
OpenSSL的RAND_bytes()
种子本身,也许通过阅读Linux中的/dev/urandom
。 如果我们需要很多随机数,那么从/dev/urandom
读取所有数据将会非常慢,因为它们必须从内核复制。 允许OpenSSL从种子生成更多的随机数字会更快。
更多关于随机数字:
- Perl的Perl_seed()是一个如何计算
srand()
中C的种子的例子。 如果不能读取/dev/urandom
,它会混合当前时间,进程ID和一些指针。 - OpenBSD的arc4random_uniform()解释了模偏移。
- java.util.Random的Java API描述了用于从随机整数中去除偏倚的algorithm,并将53位打包成随机的浮点数。
STL对于C不存在。您必须调用rand
,或者更好, random
。 这些是在标准库头stdlib.h
中声明的。 rand
是POSIX, random
是BSD spec函数。
rand
和random
之间的区别是random
返回一个更有用的32位随机数,而rand
通常返回一个16位数。 BSD手册页显示rand
的低位是循环的和可预测的,所以rand
对于小数是没有用的。
如果你的系统支持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
根据传递给它的数据填充它的内部随机数池。
如果你没有这些function,但你在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
可能会有点慢,因此build议您将其用作不同随机数生成器的种子。
如果你的系统没有/dev/urandom
,但是有一个/dev/random
或者类似的文件,那么你可以简单的改变在urandom_init
open
的path。 在urandom_init
和urandom
中使用的调用和API是(我相信)符合POSIX,因此,即使不是所有POSIX兼容的系统,也应该可以工作。
注意:如果没有足够的可用熵,则从/dev/urandom
读取将不会阻塞,因此在这种情况下生成的值可能是encryption不安全的。 如果你担心这个问题,那么使用/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; }
看看ISAAC (Indirection,Shift,Accumulate,Add,和Count)。 其分布均匀,平均周期长度为2 ^ 8295。
你想使用rand()
。 注意( 非常重要 ):确保为rand函数设置种子。 如果你不这样做,你的随机数不是真正的随机数 。 这非常非常非常重要 谢天谢地,你通常可以使用一些系统滴答计时器和date的组合来获得一个好种子。
FWIW,答案是肯定的,有一个叫做rand
的stdlib.h
函数; 这个function主要是为了速度和分配而调整的,而不是不可预测的。 几乎所有内置的各种语言和框架的随机函数默认使用这个函数。 也有“密码”随机数发生器,可预测性更差,但运行速度慢得多。 这些应该用于任何types的安全相关的应用程序。
那么,STL是C ++,而不是C,所以我不知道你想要什么。 如果你想C,但是,有rand()
和srand()
函数:
int rand(void); void srand(unsigned seed);
这些都是ANSI C的一部分。还有random()
函数:
long random(void);
但据我所知, random()
不是标准的ANSI C.第三方库可能不是一个坏主意,但这一切都取决于你真正需要产生的数字的随机性。
这是希望比只使用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()); }
rand()
是生成随机数最方便的方法。
你也可以从任何在线服务,如random.org抓住随机数。
这是一个很好的方法来获得您select的两个数字之间的随机数字。
#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
后的值更改为您select的任何数字,并在这两个数字之间为您生成一个随机数。
#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; }
#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()
在给定的范围内产生均匀分布的随机数是一个坏主意,我决定看看输出实际上是多么扭曲。 我的testing案例是掷骰子。 这是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。
在我最近的应用程序中,我使用伪随机数生成器存在一个严重的问题:我通过pyhton脚本重新调用了我的C程序,并将以下代码用作种子:
srand(time(NULL))
但是,因为:
- rand将产生相同的伪随机序列,在srand中给出相同的种子(参见
man srand
); - 如前所述,时间函数仅从第二个变为第二个:如果您的应用程序在同一秒内运行多次,则
time
将每次返回相同的值。
我的程序生成了相同的数字序列。 你可以做3件事来解决这个问题:
-
混合时间输出与一些其他信息在运行(在我的应用程序,输出名称)更改:
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是一个安全的赌注。
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 – 1和upperLimit之间生成一个随机数
即lowerLimit是包含的或者说r∈[lowerLimit,upperLimit)
在现代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
我的简约解决scheme应该适用于范围[最小,最大)的随机数字。 之前使用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; }