C语言产生随机数

1、rand函数

c语言可以使用rand函数创建随机数,其头文件为stdlib.h,具体实例如下:

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

int main(){
    for(int i=0;i<10;i++){
        printf("random number: %d\n",rand());
    }
    return 0;
}

其某次执行结果为:

random number: 1804289383
random number: 846930886
random number: 1681692777
random number: 1714636915
random number: 1957747793
random number: 424238335
random number: 719885386
random number: 1649760492
random number: 596516649
random number: 1189641421

可以看到,rand确实产生了随机数,但是随机出来的值都很大。因此我们可以合理推测,rand函数所做的事情,就是从0~RAND_MAX之中随机产生一个数,可以确定的是RAND_MAX很大,大约是一个大于200000000的十位十进制数(从输出结果反推),事实也确实如此,stdlib.h中RAND_MAX的定义为:

#define RAND_MAX        2147483647

实际上这一种随机通常派不上用场,因为需要产生随机数的应用场景,通常是从有限的内容中选出一位,比如说公司年会抽奖。那么我们需要将随机值限定在一定范围内,并保证其公平性,一种简单的实现是:

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

int main(){
    for(int i=0;i<10;i++){
        printf("random number: %d\n",rand()%10);
    }
    return 0;
}

没错,仅仅是对随机数进行模运算,此实例中利用随机数对10取余数,可以产生0~9之间的随机数,其某次随机结果为:

random number: 3
random number: 6
random number: 7
random number: 5
random number: 3
random number: 5
random number: 6
random number: 2
random number: 9
random number: 1

2、随机数种子

许多博客都提到,rand产生的随机数是伪随机数,其产生原理是一种简单的映射关系,映射,也即函数关系,其表明某一个特定的输入将产生特定的输出,这里的输入,也就是所谓的随机数种子。此外,作者们还提到,随机数种子是计算机启动时也就确定了的,也就是说如果不手动改变随机数种子,程序产生的随机数总是确定的,这与我们第一节得到的结论相悖。但是,随机数种子确实是存在的,以下实例将会证明这一观点:

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

int main(){
    for(int i=0;i<10;i++){
        //设置随机数种子
        srand(1708262157);
        printf("random number: %d\n",rand());
    }
    return 0;
}

可以看到,每次产生随机数之前都设置了随机数种子。最终此程序的输入结果为:

random number: 1237625294
random number: 1237625294
random number: 1237625294
random number: 1237625294
random number: 1237625294
random number: 1237625294
random number: 1237625294
random number: 1237625294
random number: 1237625294
random number: 1237625294

即使再重复10次、100次,结果也不会变。因此可以证明,随机数种子确实可以影响产生的随机数,如果随机数种子设置为其他值,rand产生的随机数也应该改变,这一点读者可以自行验证。

整理目前的结论,有两点:1、rand确实产生伪随机数,通过srand设置随机数种子可以让rand产生的值不再随机。2、在不做任何其他操作的情况下,rand每次得到的随机值看起来确实是随机的。

因此,我们可以合理推测,除非使用srand设定随机数种子,否则随机数种子总在变化。这个结论恰恰与许多博主提到的随机数种子在启动后不再改变(除非调用srand)相违背。

以下实例可以佐证此观点:

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

int main(){
    //设置随机数种子
    srand(1708262157);
    for(int i=0;i<10;i++){
        printf("random number: %d\n",rand());
    }
    return 0;
}

此代码与上一个实例的唯一区别的:这里将设置随机数种子的过程移出了循环,放到了main函数起始的位置。其输出结果为:

xiaoyongzhe:~/$ ./random 
random number: 1237625294
random number: 1646814452
random number: 1570692793
random number: 252462525
random number: 33054351
random number: 876560172
random number: 1732276113
random number: 1793720059
random number: 1238005361
random number: 155841260
xiaoyongzhe:~/$ ./random 
random number: 1237625294
random number: 1646814452
random number: 1570692793
random number: 252462525
random number: 33054351
random number: 876560172
random number: 1732276113
random number: 1793720059
random number: 1238005361
random number: 155841260

这里给出了两次执行的结果,可以看到,两次结果是完全一致的!并且,两次运行的第一行结果恰与上一个实例的运行结果相同。因此我们可以合理推断出,每次rand产生随机数后,总是重新设置随机数种子,并且,新的随机数种子也遵循某种规律,并且也是唯一的。

3、rand函数源码

rand的实现在各个系统中略有差异,但大约是以下形式:

#freebsd中rand函数的实现
#https://github.com/lattera/freebsd/blob/master/lib/libc/stdlib/rand.c

static int
do_rand(unsigned long *ctx)
{
#ifdef  USE_WEAK_SEEDING
/*
 * Historic implementation compatibility.
 * The random sequences do not vary much with the seed,
 * even with overflowing.
 */
	return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)RAND_MAX + 1));
#else   /* !USE_WEAK_SEEDING */
/*
 * Compute x = (7^5 * x) mod (2^31 - 1)
 * without overflowing 31 bits:
 *      (2^31 - 1) = 127773 * (7^5) + 2836
 * From "Random number generators: good ones are hard to find",
 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
 * October 1988, p. 1195.
 */
	long hi, lo, x;

	/* Must be in [1, 0x7ffffffe] range at this point. */
	hi = *ctx / 127773;
	lo = *ctx % 127773;
	x = 16807 * lo - 2836 * hi;
	if (x < 0)
		x += 0x7fffffff;
	*ctx = x;
	/* Transform to [0, 0x7ffffffd] range. */
	return (x - 1);
#endif  /* !USE_WEAK_SEEDING */
}

int
rand()
{
	return (do_rand(&next));
}

可以看出,确实与我们之前的假设相符,调用rand的时候确实也同时更新了随机数种子的值。

4、使用时间作为随机数种子

一种常见的做法是使用当前时间作为随机数种子,以期待在程序运行时不至于产生相近的随机数,实例如下:

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

int main(){
    srand(time(NULL));
    printf("random number: %d\n",rand());
    return 0;
}

多次运行此程序,产生的结果一般是不一样的。

xiaoyongzhe:~/$ ./random 
random number: 1795857324
xiaoyongzhe$ ./random 
random number: 115653895
xiaoyongzhe:~/$ ./random 
random number: 576250747
xiaoyongzhe:~/$ ./random 
random number: 1052224321
xiaoyongzhe:~/$ ./random 
random number: 1507293784

之所以说一般不一样,是因为使用time函数获取到的是当前的秒数,如果在一秒内多次产生随机数,则仍会出现不随机的情况,具体实例如下:

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

int main(){
    for(int i=0;i<10;i++){
         srand(time(NULL));
         printf("random number: %d\n",rand());
    }
    return 0;
}

其对应的输出为:

random number: 1119982487
random number: 1119982487
random number: 1119982487
random number: 1119982487
random number: 1119982487
random number: 1119982487
random number: 1119982487
random number: 1119982487
random number: 1119982487
random number: 1119982487

倘若秒数变化恰好位于for循环之间,则可能会打印出不同的随机数,当然,这一概率接近于0,以我使用的8700k为例,虚拟机中运行此循环10000次,也不过花费0.8ms,打印出不同随机数的概率仍几近为0。

因此,如果要在一秒内多次产生随机数,需要使用其他的随机数种子,如ns计时。其实例如下:

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

int main(){
    struct timespec ts;
    for(int i=0;i<10;i++){
        clock_gettime(CLOCK_REALTIME, &ts);
        srand(ts.tv_nsec);
        printf("random number: %d\n",rand());
    }
    return 0;
}

某次输出如下:

xiaoyongzhe:~/$ ./random 
random number: 1924027910
random number: 2113083654
random number: 1317190802
random number: 2146606209
random number: 1328808484
random number: 1895734045
random number: 147245493
random number: 1043139992
random number: 1168012613
random number: 608497663

参考资料:

C语言:生成随机数(并非固定的随机数)——rand()、srand()_c语言生成随机数-CSDN博客

C库:rand和srand的实现原理以及C库中源代码_c语言随机函数源代码-CSDN博客

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值