C++抽象编程——接口(5)——随机数算法

来源:互联网 发布:收购博客玩家数据 编辑:程序博客网 时间:2024/06/02 21:20

验证接口设计的最好方法是编写使用它的应用程序。例如,图2-12中的程序使用randomInteger函数来演示叫做craps的赌场游戏。
这个程序播放叫做craps的赌场游戏,它使用一对骰子玩。在游戏开始的时候,你掷骰子并计算总数。如果你的第一次是7或11,你赢得了赌徒所说的“natural”。如果你的第一次是2,3或12,你会输掉,原因是“crapping out”。在任何其他情况下,从第一次的总数成为您的“点”,之后您继续滚动骰子直到出现以下情况之一:
a)你再摇一遍你之前的数字,你赢了。
b)你摇到7,在这种情况下你输了。
其他数字,包括2,3,11和12,在这个游戏阶段没有任何影响。

建立客户端程序:

#include <iostream>#include "random.h"using namespace std;/* Function prototypes */bool tryToMakePoint(int point);int rollTwoDice();/* Main program */int main() {    cout << "This program plays a game of craps." << endl;    int point = rollTwoDice();    switch(point){        case 7: case 11:            cout << "This is a natural, you win" << endl;            case 2: case 3: case 12:                cout << "That is a crap, you lost" << endl;                default :                    cout << "you point is" << point << endl;    }    if(tryToMakePoint(point)){        cout << "you make your point, you win" << endl;    }else{        cout << "you roll a seven, you lost" << endl;    }    return 0;}int rollTwoDice(){    cout << "roll the dice...." << endl;    int d = randomInteger(1,6);    int d1 = randomInteger(1,6);    int point = d + d1;    cout << "you roll " << d << "and" << d1 << "  point =" << point << endl;     return point;}bool tryToMakePoint(int point){    while(true){        int total = rollTwoDice();        if (total == point) return true;        if (total == 7) return false;    }}

随机数算法

到目前为止,我们仅仅介绍了random.h接口的设计。在实际上可以在程序中使用该接口之前,需要编写相应的random.cpp实现。 所有你知道的是,你在< cstdlib >库中有一个函数rand,它生成一个0和正常常数RAND_MAX之间的随机整数,我们可以把它们看成一个数字行上的一些点,如下所示:

为了模仿我们的投掷骰子,我们需要将它们转换为6个整数,就像这样:

要实现这个功能,我们有很多不是特别好的策略,比如下面这个:

int die = rand() % 6 + 1;

这个算法看起来并没有太大的问题,产生的随机数用%运算得到一个整数,然后+1,就是得到了0-6之间的一个整数。
但是,这个策略的问题在于,rand只保证其产生的值均匀分布在从0到RAND_MAX的范围内。然而,不能保证除以六的余数将是随机的。事实上,使用Unix操作系统分发的rand的早期版本生成了在奇数和偶数值之间交替的值,尽管这些值确实在整个范围内均匀。将余数除以6也将在偶数和奇数值之间交替,这几乎不适合任何合理的随机行为定义。
那我们怎么去改进呢?我们要做的是将0和RAND_MAX之间的整数除以与不同结果以得到相对应的相等大小的段,如下所示:

在更一般的情况下,你需要将0和RAND_MAX之间的数字线划分为k个相等的间隔。一旦你这样做,你需要做的就是将间隔数映射到客户想要的值。将rand函数的结果转换为有限范围内的整数的过程,如果将其分解为以下四个步骤过程:
1. 归一化rand的整数结果。(Normalize)通过将其转换为0≤d<1范围内的浮点数d,就是用产生的值来除以最大值(Normalize the integer result from rand by converting it into a floating-point number d in the range 0 ≤ d < 1. )。
2. 缩放值d。(Scale)通过将值乘以所需范围的大小,以便它跨越正确数目的整数。(Scale the value d by multiplying it by the size of the desired range, so that it spans the correct number of integers
3. 转换值(Translate)。通过与下限相加转换值,使范围从所需点开始。(Translate the value by adding in the lower bound so that the range begins at the desired point
4. 转换回整数(Convert)通过从< cmath >库调用函数floor将数字转回整数,该函数返回最小的整数,它的参数较小(Convert the number back to an integer by calling the function floor from < cmath > library, which returns the largest integer that is smaller its argument

这些阶段如图下所示,它跟踪了整个过程中的一个运算路径:

现在我们来解析一下这幅图。假设rand的初始调用返回848,256,064,并且RAND_MAX的值的最常见值为2,147,483,647,则归一化过程产生接近0.4的值(图2中的值已舍入为小数点后的单个数字, 使它们适合于图)。在缩放步骤中将该值乘以6,得到2.4,然后在translate步骤中将其移动到3.4(骰子的下限为1,因为投掷一个骰子,最小点数为1,2.4+1=3.4)。在转换步骤中,3.4处产生值3。

编写代码实现

编写代码来实现这个过程并不容易,因为它可能会出现几个陷阱,如果我们不足够仔细,很容易陷入这样的陷阱。例如,考虑步骤Normalize步骤。我们如果通过下面的方式是不能通过调用将rand的结果转换为半开区间[0,1)中的浮点值:

double d = double(rand()) / RAND_MAX);

这里的问题是rand可能返回值RAND_MAX,这意味着变量d将被赋值为1.0,这是从开区间中被明确排除的。更糟糕的是你也不能写成:

double d = double(rand()) / (RAND_MAX + 1);

虽然这种情况下的问题更为微妙。如前所述,RAND_MAX通常选择为int类型的最大正值。如果真是这样,我们这么做就是在添加一个不可能产生正确结果的数,因为该值在整数范围之外。要解决这些问题,我们需要做的是使用类型double而不是int执行添加,如下所示:

double d = rand() / (double(RAND_MAX) + 1);

类似的问题还出现在Scaling步骤。在数学上,表达式给出了计算从低到高的范围的表达:

high - low + 1

然而,如果高是大的正值,则该值可以溢出一个整数的范围,因为在绝对量级数较大的情况下(即绝对值),该值可以为负数。 因此,为了从归一化值d计算标度值s,需要以双精度执行此计算:

double s = d * (double(high) - low + 1);

幸运的是,转型过程的最后两个步骤比较简单。给定缩放值s,我们可以在单个语句中将Translate和Convert步骤结合到一个语句:

return int(floor(low + s));

编写cpp文件

#include <iostream>#include <cstdlib>#include <cmath>#include "random.h"using namespace std;int randomInteger(int low, int high){    double d = rand() / (double(RAND_MAX) + 1);    double s = d * (double(high) - low + 1);    return int(floor(low + s));}double randomReal(double low, double high){    double d = rand() / (double(RAND_MAX) + 1);    double s = d * (double(high) - low);    return low + s;}bool randomChance(double p){    return randomReal(0, 1) < p;}

运行结果如下:

PS:random.h 文件参考我的博客 C++抽象编程——接口(4)——随机接口的设计
程序是可以运行了,但是真的就是随机吗?你可以多去试试,多运行几次程序,看看结果。至于为什么,我下篇博客再谈。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 开发者账号密保忘记怎么办 华为账号忘记密保问题怎么办 fiyme账号忘记密保怎么办 id忘了密保问题怎么办 vivo账号密码忘记了怎么办 步步高账号密码忘了怎么办 步步高手机账号密码忘了怎么办 康佳电视通行证忘了怎么办 尚游通行证忘了怎么办 深圳免限行通行证忘了截图怎么办 电脑把管理员账号删除了怎么办 uc新浪加载失败怎么办红包还 微信忘记账号和密码怎么办 苹果手机忘记id密码怎么办 购买游戏账号被找回怎么办 交易猫账号忘了怎么办 爱奇艺账号怎么修改不了密码怎么办 论文目录显示错误未定义书签怎么办 银行账号被冻结了怎么办 哈罗单车账号被冻结怎么办 麻袋赚赚账号被冻结怎么办 网赌账号被冻结怎么办 梦想城镇账号被冻结怎么办 钱被银行冻结了怎么办 百度云账号密码忘了怎么办 微信钱包忘记密码了怎么办 word文档打开文件出错怎么办 有盘文件删不了怎么办 u盘文档严重损坏怎么办 wps文档打开是乱码怎么办 九游3083网资金冻结怎么办 阴阳师九游版禁止部分玩法怎么办 夜神模拟器游戏打不开怎么办 九游代金券锁定怎么办 被娱乐天地骗了怎么办 win8更新失败无法开机怎么办 安装英雄联盟文件损坏怎么办 电脑玩lol运行内存不足怎么办 守望先锋账号冻结了怎么办 守望先锋服务器发生意外错误怎么办 在先锋社保缺一年上学怎么办