C/C++ 中生成特定范围内的随机数

大家在写 C/C++ 程序时,难免会遇到要求获取某个范围内的随机数,我查阅了一些资料后,总结如下。本文分两部分,先介绍 C 语言中与随机数相关的两个函数 srand 和 rand,后介绍 C++ 中的 random 库,每一部分最后会给出生成特定范围内的随机数模板供参考。


1 C 语言中的 srand 和 rand


1.1 实现

下面是 VC 的实现,GCC 的实现比 VC 的复杂,但基本原理是一样的。
#define RAND_MAX 32767	// in <stdlib.h>

unsigned long _Randseed = 1;	// global seed

void srand(unsigned int seed) {
	_Randseed = seed;
}

int rand(void) {
	_Randseed = _Randseed * 1103515245 + 12345;

	return ((unsigned int)(_Randseed >> 16) & RAND_MAX);
}

第一次接触 C 语言中的随机数时,很疑惑为什么有种子这个玩意,只提供一个产生随机数的函数不就行了吗,看了上面的源码后,就明白了,因为计算机不能产生真正的随机数,只能靠数学的方法产生伪随机数。srand 函数的作用是设置种子,如果不设置的话种子(上面的 _Randseed)则默认初始是1,种子是全局变量。rand 的实现就跟数论有关了,上面的实现用的是线性同余法。可以看到它的返回值范围是 [0, RAND_MAX]。


1.2 time

既然计算机不能产生真正的随机数,那怎么才能使程序每次运行的结果不同呢?总得有个随机的东西,那就借助 time 这个函数产生种子,引入一个新东西又会带来一些坑,我早年写过这种程序:

// 生成十个随机数(错误用法)
for (int i = 0; i < 10; ++i) {
	srand((unsigned int)time(NULL));
	printf("%d: %d\n", i, rand());
}
它将产生10个相同的数。要解释这个问题,就得弄懂 time 这个函数,它的函数原型如下:
time_t time(time_t *timer);

它返回“当前时间”,这个“时间“的类型是 time_t,在 VC 中被 typedef 为 unsigned long,标准中只规定它是个算数类型,至于它是如何表示时间的未定义。一般是返回 UNIX 时间戳,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。上面的程序执行时很快,在一秒内完成循环,所以它产生了相同的随机数。


1.3 my rand

下面提供两个生成随机数的模板。

int g_is_first = 1;

/*
** return a random integer in the interval
** [a, b]
*/
int uniform_int(int a, int b) {
	if (g_is_first) {
		g_is_first = 0;
		srand((unsigned int)time(NULL));
	}

	return (int)((double)rand() / ((RAND_MAX + 1.0) / (b - a + 1.0)) + a);
}

/*
** return a random real in the interval
** [a, b] (also [a, b))
*/
double uniform_real(double a, double b) {
	if (g_is_first) {
		g_is_first = 0;
		srand((unsigned int)time(NULL));
	}

	return (double)rand() / ((double)RAND_MAX / (b - a)) + a;
}
为了保证 srand 函数只执行一次,这里用了全局标志 g_is_first。其实最好在头文件中定义接口,在源文件中实现,这里为了使用方便就全放一起了。当要求的随机数范围过大时,uniform_int 和 uniform_real 貌似有 bug。


2 C++ 中的 random 库


在 random 库中有随机数发生器(random engine/generator)和分布(distribution),它们的具体用法我就不在这说了。我个人认为 engine 存储了种子,将 C 语言中的全局种子封装起来了。uniform distribution 中只存储了最大值和最小值(即平均分布的两个参数)。还有个真正的 engine 叫 std::random_device,它根据机器的各种实时参数产生随机数,标准规定它的实现是可选的,有的编译器(如 MinGW)目前不支持,产生的还是伪随机数,不过 VC 及 Linux 平台上的 GCC 是支持的,下面的程序假设用户的编译器支持。
/*
** return a random integer in the interval [a, b]
*/
int uniform_int(int a, int b) {
	static std::default_random_engine e{std::random_device{}()}; // avoid "Most vexing parse"
	static std::uniform_int_distribution<int> u;

	return u(e, decltype(u)::param_type(a, b));
}

/*
** return a random real in the interval [a, b] (also [a, b))
*/
double uniform_real(double a, double b) {
	static std::default_random_engine e{std::random_device{}()};
	static std::uniform_real_distribution<double> u;

	return u(e, decltype(u)::param_type(a, b));
}
最后,还有个问题没搞懂:能否始终用 random_device 代替 default_random_engine ?它们的区别是什么?

参考资料

[1] C 标准库
[2] C 和指针
[8] C++ Primer
  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++ Primer文版(第5版)[203M]分3个压缩包 本书是久负盛名的C++经典教程,其内容是C++大师Stanley B. Lippman丰富的实践经验和C++标准委员会原负责人Josée Lajoie对C++标准深入理解的完美结合,已经帮助全球无数程序员学会了C++。 对C++基本概念和技术全面而且权威的阐述,对现代C++编程风格的强调,使本书成为C++初学者的最佳指南;对于高级程序员,本书也是不可或缺的参考书。 目录 第1章 开始 1   1.1 编写一个简单的C++程序 2   1.1.1 编译、运行程序 3   1.2 初识输入输出 5   1.3 注释简介 8   1.4 控制流 10   1.4.1 while语句 10   1.4.2 for语句 11   1.4.3 读取数量不定的输入数据 13   1.4.4 if语句 15   1.5 类简介 17   1.5.1 Sales_item类 17   1.5.2 初识成员函数 20   1.6 书店程序 21   小结 23   术语表 23   第Ⅰ部分 C++基础 27   第2章 变量和基本类型 29   2.1 基本内置类型 30   2.1.1 算术类型 30   2.1.2 类型转换 32   2.1.3 字面值常量 35   2.2 变量 38   2.2.1 变量定义 38   2.2.2 变量声明和定义的关系 41   2.2.3 标识符 42   2.2.4 名字的作用域 43   2.3 复合类型 45   2.3.1 引用 45   2.3.2 指针 47   2.3.3 理解复合类型的声明 51   2.4 const限定符 53   2.4.1 const的引用 54   2.4.2 指针和const 56   2.4.3 顶层const 57   2.4.4 constexpr和常量表达式 58   2.5 处理类型 60   2.5.1 类型别名 60   2.5.2 auto类型说明符 61   2.5.3 decltype类型指示符 62   2.6 自定义数据结构 64   2.6.1 定义Sales_data类型 64   2.6.2 使用Sales_data类 66   2.6.3 编写自己的头文件 67   小结 69   术语表 69   第3章 字符串、向量和数组 73   3.1 命名空间的using声明 74   3.2 标准库类型string 75   3.2.1 定义和初始化string对象 76   3.2.2 string对象上的操作 77   3.2.3 处理string对象的字符 81   3.3 标准库类型vector 86   3.3.1 定义和初始化vector对象 87   3.3.2 向vector对象添加元素 90   3.3.3 其他vector操作 91   3.4 迭代器介绍 95   3.4.1 使用迭代器 95   3.4.2 迭代器运算 99   3.5 数组 101   3.5.1 定义和初始化内置数组 101   3.5.2 访问数组元素 103   3.5.3 指针和数组 105   3.5.4 C风格字符串 109   3.5.5 与旧代码的接口 111   3.6 多维数组 112   小结 117   术语表 117   第4章 表达式 119   4.1 基础 120   4.1.1 基本概念 120   4.1.2 优先级与结合律 121   4.1.3 求值顺序 123   4.2 算术运算符 124   4.3 逻辑和关系运算符 126   4.4 赋值运算符 129   4.5 递增和递减运算符 131   4.6 成员访问运算符 133   4.7 条件运算符 134   4.8 位运算符 135   4.9 sizeof运算符 139   4.10 逗号运算符 140   4.11 类型转换 141   4.11.1 算术转换 142   4.11.2 其他隐式类型转换 143   4.11.3 显式转换 144   4.12 运算符优先级表 147   小结 149   术语表 149   第5章 语句 153   5.1 简单语句 154   5.2 语句作用域 155   5.3 条件语句 156   5.3.1 if语句 156   5.3.2 switch语句 159   5.4 迭代语句 165   5.4.1 while语句 165   5.4.2 传统的for语句 166   5.4.3 范围for语句 168   5.4.4 do while语句 169   5.5 跳转语句 170   5.5.1 break

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值