前言

随机数是人们生活中的必需品,比如说喝酒时的划拳,骰子,国人喜欢的斗地主,麻将,福彩,游戏中那就跟不用说了。所以说随机数的设计是关乎公平性最重要的决定因素。如果说前面提到的事件都可以预测的话,我想没有人会去参与这些事件。

随机数的用途

  • 数学 (统计计算, 模拟)
  • 游戏(随机掉落宝物,爆击概率)
  • 安全(随机密码,证书)
  • 测试(白盒测试)

随机数生成器的类型

  • 物理模型 (掷骰子,掷硬币,白噪声。。。)
  • 数学模型

随机数的生成方法


物理的方法是根据实验结果通过hash 函数 对应成数据流,在计算机编程中可能不会用到那么复杂的随机数生成器。这时可以运用现成的随机发生器来模拟物理生成器。比如说socket发送过来的时间,键盘,鼠标的事件,当前占用内存最大的pid值。都是用来产生随机数的好的素材。

数学模型是通过公用的公式:

X(n+1) = (a * X(n) + c) % m。对应参数值:

模m, m > 0

系数a, 0 < a < m

增量c, 0 <= c < m

原始值(种子) 0 <= X(0) < m

其中参数c, m, a比较敏感,或者说直接影响了伪随机数产生的质量。



随机种子

随机算法都是通用的,程序中遇到不同的问题,需要根据情况来决定提供什么样的随机种子。通过上面的公式我们知道如果提供相同的种子每次产生的随机序列是一样的。所以程序中常用的方法是用当前时间来做随机算法的种子。但是通过当前时间就一定能每次产生的随机数是不一样的吗?

#include <iostream>
#include <ctime>

int GetRandNum(int min, int max) {
    srand(time(NULL));
    return (min + rand() %(max - min +1));
}
int main() {
    std::cout<<"the num is:";
    for(int i = 0; i < 10; ++i) {
        std::cout<<GetRandNum(0, 10)<<"\t";
    }
    std::cout<<"\n";
}

java随机运算符号_c/c++

为什么结果都是一样的呢,这就是前面说的种子一样的话每次产生的随机数是一样的,因为time(NULL) 只能精确到秒,所以在一秒内程序很轻松执行完。遇到这种问题如何解决呢,当然用精度更高的纳秒是一种解决办法,也可以用当前的随机数做为下次随机的种子也是一种好的方式。

常用随机数算法

boost库自己提供的随机数发生器:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>

int main() {
    boost::random::mt19937 rng;
    boost::random::uniform_int_distribution<int> index_dis(0, 10);
    std::cout<<"the num is:";
    for(int i =0; i < 10; ++i) {
        std::cout<<index_dis(rng)<<"\t";
    }
    std::cout<<"\n";
    return 0;

}

java随机运算符号_c/c++_02


自己实现的随机发生器:



#include <iostream>
#include <ctime>

static int seed = time(NULL);
int GetRandNum(int min, int max) {
    srand(seed);
    seed = rand();
    return (min + rand() %(max - min +1));
}
int main() {
    std::cout<<"the num is:";
    for(int i = 0; i < 10; ++i) {
        std::cout<<GetRandNum(1, 10)<<"\t";
    }
    std::cout<<"\n";
}

java随机运算符号_#include_03

facebook的随机种子:

#include "folly/Random.h"

#include <unistd.h>
#include <sys/time.h>

namespace folly {

uint32_t randomNumberSeed() {
  struct timeval tv;
  gettimeofday(&tv, NULL);
  const uint32_t kPrime1 = 61631;
  const uint32_t kPrime2 = 64997;
  const uint32_t kPrime3 = 111857;
  return kPrime1 * static_cast<uint32_t>(getpid())
       + kPrime2 * static_cast<uint32_t>(tv.tv_sec)
       + kPrime3 * static_cast<uint32_t>(tv.tv_usec);
}

}



随机数生成器效率


通过生成10,000,000随机数来测试效率和随机数的分布。

boost的随机发生器


#include <boost/random/mersenne_twister.hpp>
#include <boost/random/discrete_distribution.hpp>
#include <iostream>
#include <map>
#include <ctime>
#include <sys/time.h>
#define TEST_COUNT 10000000
int main() {
    boost::mt19937 gen;
    double probabilities[] ={0.1, 0.12, 0.2, 0.26, 0.32};
    boost::random::discrete_distribution<> dist(probabilities);

    struct timeval tim;
    gettimeofday(&tim, NULL);
    std::cout<<"start time:"<<tim.tv_sec<<"the micro sec is:"<<tim.tv_usec<<"\n";
    std::map<float, int> statis_map;
    for(int i = 0; i < TEST_COUNT; ++i) {
        statis_map[probabilities[dist(gen)]]++;
    }
    gettimeofday(&tim, NULL);
    std::cout<<"end time:"<<tim.tv_sec<<"the micro sec is:"<<tim.tv_usec<<"\n";

    for(std::map<float, int>::iterator iter = statis_map.begin(); iter != statis_map.end(); ++iter) {
        std::cout<<"the per:"<<iter->first<<"\tresult per:"<<(float)iter->second/TEST_COUNT<<"\n";
    }
    return 0;
}



结果为:

java随机运算符号_java随机运算符号_04

自制随机发生器


#include <iostream>
#include <cmath>
#include <ctime>
#include <map>
#include <sys/time.h>
using namespace std;
#define TEST_COUNT 10000000
static int s_rand = time(NULL);
float RandomFloat()
{   
    srand(s_rand);
    int i =  rand();
    s_rand = i;
    return (float)(i%100)/100;
}

int main() 
{
    float per_array[] ={0.1, 0.12, 0.2, 0.26, 0.32};

    timeval tim;
    gettimeofday(&tim, NULL);
    
    cout<<"start time:"<<tim.tv_sec<<"the micro sec is:"<<tim.tv_usec<<"\n";
    map<float, int> per_map;     
    for(int i = 0; i < TEST_COUNT; ++i) {
        float frand = RandomFloat();
        float min = 0;
        float max = 1.0f;
        for(int i =0; i < 5; ++i){
            min = max - per_array[i];
            if(frand >= min){
                per_map[per_array[i]]++; 
                break;
            }
            max = min;
        }
    }
    
    gettimeofday(&tim, NULL);
    cout<<"end time:"<<tim.tv_sec<<"the micro sec is:"<<tim.tv_usec<<"\n";
    map<float, int>::iterator iter = per_map.begin(); 
    for(;iter != per_map.end(); ++iter) {
        float per_count = (float)iter->second;
        cout<<"the per:"<<iter->first<<"\tresult per:"<<per_count/TEST_COUNT<<endl;
    }
    return 0;
}



结果为:

java随机运算符号_ios_05



通过分析得知,两个随机发生器的概率分布,基本上和给出的概率非常吻合,运算速度的话,自制的随机发生器比boost几乎快一倍。经常听到很多人不要重新制造轮子,吐槽c++的程序员喜欢自造轮子。确实已经存在的东西没必要再去重写一边,因为那是浪费时间。但是并不代表说不会造轮子,假如都不知道如何造,又如何分辨轮子的好坏,哪些程序岂不要考蒙来写。那写出来的代码质量也是按随机概率分布的。。。。。


有意思的随机程序


boost生成随机密码

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>

int main() {
    std::string chars(
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "1234567890"
        "!@#$%^&*()"
        "`~-_=+[{]{\\|;:'\",<.>/? ");
    boost::random::mt19937 rng;
    boost::random::uniform_int_distribution<int> index_dist(0, chars.size() - 1);
    std::cout<<"You pwd is:\t";
    for(int i = 0; i < 8; ++i) {
        std::cout << chars[index_dist(rng)];
    }
    std::cout << std::endl;
}

结果:

java随机运算符号_#include_06

自制随机发生器生成密码


#include <iostream>
#include <string>
#include <ctime>
using namespace std;

static int seed = time(NULL);
int RandomRange(int min, int max) {
    srand(seed);
    seed = rand();
    return (min + (rand())%(max-min));
}
char RollChar() {
     std::string chars(
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "1234567890"
        "!@#$%^&*()"
        "`~-_=+[{]{\\|;:'\",<.>/? ");
    char ch = chars[RandomRange(0, chars.size())];
    return ch;
}

string RollString(int min, int max) {
    int str_len = RandomRange(min, max);

    string str(str_len, '0');
    for(int i = 0; i < str_len; ++i) {
        str[i] = RollChar();
    }
    return str;

}

int main() {
    int count, min_length, max_length;
    cout<<"the count you want to:\n";
    cin>>count;
    cout<<"the min, and max username length:\n";
    cin>>min_length>>max_length;
    if(min_length > max_length || min_length < 0) {
        cout<<"you stupid boy!!!\n";
        return 1;
    }

    for(int i = 0; i < count; ++i) {
        cout<<"the user name is:\t"<<RollString(min_length, max_length)<<"\tThe pwd:\t"<<RollString(8,10)<<endl;
    }
    return 0;
}

结果:

java随机运算符号_#include_07

可以根据这个算法写个生成账号密码的软件,专门来管理自己日常的密码,可以生成变态的用户名密码。