在上一篇文章(http://blog.csdn.net/xzjxylophone/article/details/6853589)的最后我们提出如何用Rand7实现比较大的数字,例如Rand35,Rand34等等
这篇文章中我们来尝试新建一个类:RandN表示是任意小于49的一个随机数
首先看Rand10_3,我们可以这样思考:10=7*1 + 3,因此我们再第二步使用了如下的语句:
//num=4,5,6,7的时候重新计算
if(num > 3)
{
result = Next();
}
把num=4,5,6,7的数字给过滤掉,重新去计算Rand10
因此我们可以这样去计算RandN(以34为例子):
34=4*7 + 6,
先得到一个Rand7的数字:int remain = rand7.Next()
然后在得到数字:int ceiling = _rand7.Next();//ceiling
如果ceiling 大于5,即ceiling =6,7
重新计算ceiling = _rand7.Next();//ceiling
否则
result=remain + 7 * ceiling;
在这里还要判断条件,就是remain这个参数:
如果remain大于6的话,那么就重新计算。
以下是RandN的实现:
//支持2-49,包括2和49
public class RandN : Rand7Base
{
protected int _remain; //剩余数,范围是0-6
protected int _ceiling; //大于的最小数
private RandN(int maxNum)
{
UpdateMaxNum(maxNum);
}
public void UpdateMaxNum(int maxNum)
{
_maxNum = maxNum;
_remain = _maxNum % 7;
decimal chushu = (decimal)_maxNum / 7;
_ceiling = (int)Math.Ceiling(chushu);
}
public static RandN GetInstance(int maxNum)
{
if (rand == null || !(rand is RandN))
{
rand = new RandN(maxNum);
}
//最大随机数不一样
else if(rand is RandN && rand.MaxNum != maxNum)
{
((RandN)rand).UpdateMaxNum(maxNum);
}
return (RandN)rand;
}
override public int Next()
{
int result = 0;
int remain = _rand7.Next();
bool loop = false;
int ceiling = 0;
while (!loop)
{
ceiling = _rand7.Next();//ceiling
//重新计算
if (ceiling > _ceiling)
continue;
//n-1保证result是有可能小于7
result = remain + (ceiling - 1) * 7;
//以下的代码可以被替代:
if (ceiling == _ceiling && _remain != 0)//考虑整除的情况
{
if (remain > _remain)//剩余数超过最大的剩余数就重新计算
{
result = Next();
}
}
//可以被替代成:
//if(result > _maxNum)
//{
// result = Next();
//}
loop = true;
}
return result;
}
}
添加测试代码:
static void TestRandN()
{
RandN rand = RandN.GetInstance(34);
TestRand(rand);
rand = RandN.GetInstance(35);
TestRand(rand);
rand = RandN.GetInstance(6);
TestRand(rand);
}
测试结果:
计算Rand34的概率如下
产生1的概率是:0.0294017
产生2的概率是:0.0294027
产生3的概率是:0.0293998
产生4的概率是:0.0295281
产生5的概率是:0.0294838
产生6的概率是:0.0294388
产生7的概率是:0.0293086
产生8的概率是:0.0294455
产生9的概率是:0.0293082
产生10的概率是:0.0294202
产生11的概率是:0.0294208
产生12的概率是:0.0293530
产生13的概率是:0.0294430
产生14的概率是:0.0293526
产生15的概率是:0.0294120
产生16的概率是:0.0293657
产生17的概率是:0.0294057
产生18的概率是:0.0293856
产生19的概率是:0.0293732
产生20的概率是:0.0294238
产生21的概率是:0.0293502
产生22的概率是:0.0294515
产生23的概率是:0.0294136
产生24的概率是:0.0294762
产生25的概率是:0.0294245
产生26的概率是:0.0294585
产生27的概率是:0.0294599
产生28的概率是:0.0293607
产生29的概率是:0.0294267
产生30的概率是:0.0294665
产生31的概率是:0.0293348
产生32的概率是:0.0294346
产生33的概率是:0.0293828
产生34的概率是:0.0294867
耗时: 2.78373975842868 秒
Average:0.0294118;Max:0.0295281;Min:0.0293082
Max:0.0295281, Max-Average:0.0001163, Bits:0.3955400%
Min:0.0293082, Min-Average:-0.0001036, Bits:-0.3521200%
计算Rand35的概率如下
产生1的概率是:0.0286397
产生2的概率是:0.0285668
产生3的概率是:0.0286318
产生4的概率是:0.0285731
产生5的概率是:0.0285246
产生6的概率是:0.0285689
产生7的概率是:0.0284451
产生8的概率是:0.0287106
产生9的概率是:0.0285398
产生10的概率是:0.0284319
产生11的概率是:0.0285941
产生12的概率是:0.0285509
产生13的概率是:0.0286407
产生14的概率是:0.0286336
产生15的概率是:0.0285854
产生16的概率是:0.0286022
产生17的概率是:0.0286192
产生18的概率是:0.0285978
产生19的概率是:0.0286203
产生20的概率是:0.0286015
产生21的概率是:0.0285558
产生22的概率是:0.0285200
产生23的概率是:0.0285130
产生24的概率是:0.0285547
产生25的概率是:0.0285327
产生26的概率是:0.0285055
产生27的概率是:0.0285340
产生28的概率是:0.0285735
产生29的概率是:0.0285664
产生30的概率是:0.0285509
产生31的概率是:0.0286140
产生32的概率是:0.0285983
产生33的概率是:0.0284317
产生34的概率是:0.0286959
产生35的概率是:0.0285756
耗时: 3.08980588360172 秒
Average:0.0285714;Max:0.0287106;Min:0.0284317
Max:0.0287106, Max-Average:0.0001392, Bits:0.4871000%
Min:0.0284317, Min-Average:-0.0001397, Bits:-0.4890500%
计算Rand6的概率如下
产生1的概率是:0.1666733
产生2的概率是:0.1664507
产生3的概率是:0.1666722
产生4的概率是:0.1667893
产生5的概率是:0.1667764
产生6的概率是:0.1666381
耗时: 9.59583967306935 秒
Average:0.1666667;Max:0.1667893;Min:0.1664507
Max:0.1667893, Max-Average:0.0001226, Bits:0.0735800%
Min:0.1664507, Min-Average:-0.0002160, Bits:-0.1295800%
观察上述结果,误差都是在我们所在的范围内,但是有一点不知道有没有注意到,就是在计算Rand6的时候居然用了9.5秒。
观察代码可以发现当maxNum为6的时候,第二次ceiling=rand7.Next() 此时必须是1,否则是一直重新计算。所以是否可以用一个办法来优化是何时来重新计算ceiling。
在C#中可以用委托,或者C++中的函数指针。下一篇文章我们来优化这个算法。