最近准备刷题,打算简单封装下随机数生成器,方便产生测试数据。C++11的STL提供了很多分布类型,我比较常用的是均匀分布,均匀分布的值有两种类型,一类是整数,另一类是浮点数,STL根据值的类型定义了两个函数 std::uniform_int_distributionstd::uniform_real_distribution 。为了方便使用,我期望在使用的时候通过函数模板的实参推导出要生成的数值类型,而不是显式指定要生成的数值类型。

判断模板实参类型

上面这个需求很简单,最开始想到的方式是对模板实参推断的类型进行判断,根据判断结果做不同的处理。

#include <random>
#include <type_traits>
#include <functional>
#include <limits>std::default_random_engine e;template<typename T_>
void Randoms(size_t counts, T_ min, T_ max) {std::function<T_()> distribute_;if (std::is_integral<T_>::value) {// lambdadistribute_ = [=]() {// 类模板特化 默认result_type int;std::uniform_int_distribution<> u(min, max);return u(e);};} else {// 类模板特化 默认result_type double;std::uniform_real_distribution<> u(min, max);// std::bind// 成员函数返回值默认类型为double;using type_ = double (std::uniform_real_distribution<>::*)(std::default_random_engine &);distribute_ = std::bind(static_cast<type_>(&std::uniform_real_distribution<>::operator()),&u,e);}for (int i = 0; i < counts; ++i) {std::cout << distribute_() << " ";}std::cout << std::endl;
}int main(int argc, char **argv) {Randoms(5,std::numeric_limits<std::size_t>::min(),std::numeric_limits<std::size_t>::max());Randoms(5, -0.5, 0.5);return 0;
}

从这个测试实例的结果来看,产生的数值远远超过了默认的 int 类型所能表示的数值,然后去看了看具体实现。下面代码是MinGW中GCC的实现版本,从实现中可以看出,均匀分布的类模板重载了函数调用运算符,接收两个参数,一个是均匀分布的随机数生成器,第二个是参数类型。首先定义了三个类型别名:

  • 生成器的结果类型 _Gresult_type
  • 无符号的结果类型 __utype,由于类模板推断的结果类型是 int_utype 的实际类型就是 unsigned int
  • 生成器的结果类型和无符号的结果类型的共同类型 __uctype, 该类型只有当 _Gresult_type__utype 可以相互转换才存在,这个过程实际就是类型转换。

随后使用 __uctype类型别名又定义了下面几个关键的变量,简单来看,生成器相关的属性值(如最大、最小值等)与随机数引擎有关,只要随机数引擎不变,生成器的属性值应该就不会变(至于属性值到底变不变以及是否与随机数种子有关等,待以后有时间再看看那部分源码怎么写的,目前就按不变来理解)。

  • __urngmin 表示生成器的最小值,不会改变;
  • __urngmax 表示生成器的最大值,不会改变;
  • __urngrange 表示生成器的数值范围,不会改变;
  • __urange 表示参数类型的数值范围,在放大操作时会随着递归进行改变;
  • __ret 表示返回结果的一部分,加上参数范围的最小值即为最终的随机数结果。

当生成器的范围超过了参数类型的范围,那么生成器生成的数值则可能超过参数类型的范围,这时候就需要进行缩小操作。缩小操作首先计算出缩小因子,然后根据缩小因子计算出缩小后符合参数类型的生成器的最大值,只要生成器生成的值超过了允许的最大值则继续生成下一个,直至生成符合要求的数值,最后将生成器的数值按比例缩小。

当生成器的范围低于参数类型的范围,那么就无法生成超过生成器范围,低于参数类型范围的数值,这时候就需要进行放大操作。放大操作是一个循环递归,递归终止的条件则是生成器的范围大于等于参数类型的范围,当生成器生成的值超过了参数类型的范围,说明生成的数值不正确,需要继续重新生成。根据随机数计算公式可以看到,high 的区间是 [0,urange / (urngrange + 1)]low 的区间是 [0, urngrange],这两个区间左右两侧都是闭区间,那么根据 (urngrange + 1) * high + low 计算的区间则是[0,urange + low],因此生成的数值可能比 urange 大,这个可能性被第一个循环条件处理了。从循环的条件看还有一个判断条件,这个条件还没太理解,初步猜测与数值溢出有一定关系。如果 __tmp 已经处于最大值,此时再加上一个非0的随机数,那么则可能超过 __ret 本身所能表示的范围,导致溢出。

template <typename _IntType>
template <typename _UniformRandomNumberGenerator>
typename uniform_int_distribution<_IntType>::result_type
uniform_int_distribution<_IntType>::
operator()(_UniformRandomNumberGenerator &__urng,const param_type &__param)
{typedef typename _UniformRandomNumberGenerator::result_type_Gresult_type;typedef typename std::make_unsigned<result_type>::type __utype;typedef typename std::common_type<_Gresult_type, __utype>::type__uctype;const __uctype __urngmin = __urng.min();const __uctype __urngmax = __urng.max();const __uctype __urngrange = __urngmax - __urngmin;const __uctype __urange = __uctype(__param.b()) - __uctype(__param.a());__uctype __ret;if (__urngrange > __urange){// downscalingconst __uctype __uerange = __urange + 1; // __urange can be zeroconst __uctype __scaling = __urngrange / __uerange;const __uctype __past = __uerange * __scaling;do__ret = __uctype(__urng()) - __urngmin;while (__ret >= __past);__ret /= __scaling;}else if (__urngrange < __urange){// upscaling/*Note that every value in [0, urange]can be written uniquely as(urngrange + 1) * high + lowwherehigh in [0, urange / (urngrange + 1)]andlow in [0, urngrange].*/__uctype __tmp; // wraparound controldo{const __uctype __uerngrange = __urngrange + 1;__tmp = (__uerngrange * operator()(__urng, param_type(0, __urange / __uerngrange)));__ret = __tmp + (__uctype(__urng()) - __urngmin);} while (__ret > __urange || __ret < __tmp);}else__ret = __uctype(__urng()) - __urngmin;return __ret + __param.a();
}

std::enable_if 模板元方法

std::enable_if 的定义如下:

template< bool B, class T = void >
struct enable_if;

std::enable_if 实现的功能是根据类模板参数 B 来决定是否定义类型 T 。它是一种元函数,利用 SFINAE 根据类型特征有条件地从重载解析中删除函数,并为不同的类型特征提供单独的函数重载和特化 std :: enable_if 可用作附加函数参数(不适用于运算符重载),返回类型(不适用于构造函数和析构函数)或用作类模板或函数模板参数。参考 https://en.cppreference.com/w/cpp/types/enable_if

SFINAE“Substitution Failure Is Not An Error” 的简写,表示替换失败不是错误,这个规则在函数模板的重载解析中经常被使用,当特化发生替换失败时,这个特化会被从函数集中删除掉,而不是导致一个编译错误。

根据这个原则,只要通过返回值进行区分,就能实现这两个函数的封装了,非常简单明了。

#include <random>
#include <type_traits>
#include <functional>
#include <limits>
#include <algorithm>class Randoms {
public:template<typename T_>std::vector<T_> operator()(std::size_t counts, T_ min, T_ max) {std::vector<T_> vec;vec.reserve(counts);for (int i = 0; i < counts; ++i) {vec.push_back(generate_(min, max));}return std::move(vec);}private:std::default_random_engine e;template<typename T_>std::enable_if_t<std::is_integral<T_>::value, T_>generate_(T_ min, T_ max) {std::uniform_int_distribution<T_> u(min, max);return u(e);}template<typename T_>std::enable_if_t<std::is_floating_point<T_>::value, T_>generate_(T_ min, T_ max) {std::uniform_real_distribution<T_> u_real(min, max);return u_real(e);}
};int main(int argc, char **argv) {auto int_result = Randoms()(5,std::numeric_limits<std::size_t>::min(),std::numeric_limits<std::size_t>::max());auto real_result = Randoms()(5, -0.5, 0.5);for (const auto &ci : int_result) {std::cout << ci << " ";}std::cout << std::endl;std::for_each(real_result.cbegin(),real_result.cend(),[](const auto &cr) { std::cout << cr << " "; });std::cout << std::endl;return 0;
}

本文博客地址

封装不同类模板的随机数生成器相关推荐

  1. C#底层库--随机数生成器

    系列文章 C#底层库–记录日志帮助类 本文链接:https://blog.csdn.net/youcheng_ge/article/details/124187709 C#底层库–MySQLBuild ...

  2. luogu P3306 [SDOI2013] 随机数生成器(BSGS,数列求通项,毒瘤特判)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 发个水题的 题解证明我还在() luogu P3306 [SDOI2013] 随机数生成器 Webli ...

  3. 开源Math.NET基础数学类库使用(13)C#实现其他随机数生成器

    原文:[原创]开源Math.NET基础数学类库使用(13)C#实现其他随机数生成器                本博客所有文章分类的总目录:http://www.cnblogs.com/asxiny ...

  4. 数论练习二之BSGS算法——随机数生成器,Matrix,Lunar New Year and a Recursive Sequence,Fermat‘s Last Theorem

    [SDOI2013] 随机数生成器 description solution 肯定是非常想找一个通项公式来表示第nnn个数的 依据形式,考虑化成等比数列 xi+1+k=a(xi+k)=a⋅xi+b+t ...

  5. Java 随机数生成器 Random SecureRandom 原理分析

    文章目录 java.util.Random java.Security.SecureRandom /dev/random 与 /dev/urandom 资料 Java 里提供了一些用于生成随机数的工具 ...

  6. C++ - 随机数生成器(random-number generator) 的 详解 及 代码

    随机数生成器(random-number generator) 的 详解 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/ ...

  7. Java 随机数生成器 Random SecureRandom

    Java 里提供了一些用于生成随机数的工具类,这里分析一下其实现原理,以及他们之间的区别.使用场景. java.util.Random Random 是比较常用的随机数生成类,它的基本信息在类的注释里 ...

  8. Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom

    Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom 文中的 Random即:java.util.Random, ThreadLocalRandom 即 ...

  9. P5147 随机数生成器 [数列]

    P5147 随机数生成器 数学老师看不懂系列 看题目这一片代码就很晕: int work(int x) {if(x==1)return 0;else return work(rand(1,x))+1; ...

最新文章

  1. 关于自动驾驶车安全保证、验证和认证的综述
  2. 【计算机网络】数据链路层 : 轮询访问 介质访问控制 ( 轮询协议 | 令牌传递协议 )
  3. 快速学习者的高效学习策略
  4. 玩转Linux必备知识(一)
  5. 1 java开发工具IDEA的使用
  6. C++string容器-构造函数
  7. eeg数据集_运动想象,情绪识别等公开数据集汇总
  8. 华为云FusionInsight MRS:助力企业构建“一企一湖,一城一湖”
  9. Spark Structured SQL : NumberFormatException: Zero length BigInteger
  10. PyCharm中导入数据分析库
  11. 去掉Mac窗口截图自带的阴影?
  12. 遍历一个List的几种方法
  13. C++ WinHTTP实现文件下载
  14. 2020ICPC上海 E.The Journey of Geor Autumn
  15. 强烈推荐张玉宏《深度学习之美》成书前的《深度学习系列十四篇》
  16. mysql数据库默认密码在哪看_怎么查看mysql数据库的登录名和密码
  17. python在mac模拟鼠标点击_如何使用Python在Mac中控制鼠标?
  18. 比起结果过程更加重要
  19. 网页中嵌入QQ和邮箱
  20. mysql 关联查询

热门文章

  1. windows下system函数的使用
  2. 关于javascript作用域
  3. 【转载】 安卓版手机微信如何清理微信空间
  4. UVa202Repeating Decimals (循环小数)
  5. 内存分配详解 malloc, new, HeapAlloc, VirtualAlloc,GlobalAlloc
  6. MyEclipse连接MySQL
  7. Spring学习笔记(三)
  8. Spring方法注入 @Lookup注解使用
  9. OPENCV-6 学习笔记
  10. AODV中实施watchdog