31、C++ 标准库扩展与应用:时间、复数、大整数及随机数处理

C++ 标准库扩展与应用:时间、复数、大整数及随机数处理

1. 为 <chrono> 高分辨率时钟提供 now() 函数

<chrono> 库是 C++ 标准库中支持时间计时功能的部分,包含系统时钟和高分辨率时钟等多种时钟支持。高分辨率时钟 std::chrono::high_resolution_clock 适合在实时 C++ 项目中提供时基。

以下是 std::chrono::high_resolution_clock 的潜在概要:

namespace std { namespace chrono {
class high_resolution_clock
{
public:
    // 时钟分辨率为微秒
    typedef chrono::microseconds duration;
    // 表示、周期和时间点的类型
    typedef duration::rep rep;
    typedef duration::period period;
    typedef chrono::time_point<high_resolution_clock,
                               duration> time_point;
    // 计数器是稳定的,即调用 now() 总是返回比前一次调用更晚的时间
    static constexpr bool is_steady = true;
    // 特定平台的 now() 实现,已声明但未实现
    static time_point now() noexcept;
};
} } // namespace std::chrono

now() 函数作为高分辨率时钟的时基,在一些编译器中可能仅被声明而未实现。这是因为标准库作者难以知晓具体使用的定时器或计数器外设及其频率。因此,可能需要手动为 std::chrono::high_resolution_clock 类的 now() 函数编写用户实现。

以下是一个可能的 now() 实现:

// 为标准库的高分辨率时钟实现 std::chrono::high_resolution_clock::now()
std::chrono::high_resolution_clock::time_point
high_resolution_clock::now()
{
    // 高分辨率时钟源为微秒
    typedef
        std::chrono::time_point<high_resolution_clock,
                                microseconds> from_type;
    // 获取一致的微秒滴答数
    // 此函数应在 mcal 中
    auto microsecond_tick
        = consistent_microsecond_tick();
    // 现在获取微秒级的时间点
    auto from_micro
        = from_type(microseconds(microsecond_tick));
    // 返回微秒级的持续时间
    return time_point_cast<duration>(from_micro);
}

高分辨率时钟的时基通常为微秒,但根据项目需求、微控制器性能和外设能力,也可选择其他时基,如毫秒和纳秒。

2. 扩展复数数模板

扩展复数数模板包含表示复数的类以及用于复值算术和基本超越函数的过程函数。扩展复数类表示的复数,其实部和虚部可以由内置类型或用户定义类型组成,并非严格限于内置类型。

以下是扩展复数模板的概要:

// 头文件 extended_complex.h
namespace extended_complex
{
    template<typename T>
    class complex
    {
        // 扩展复数模板(用户定义类型)
        ...
    }
    template<>
    class complex<float>
    {
        // 扩展复数模板(float 类型)
        ...
    }
    template<>
    class complex<double>
    {
        // 扩展复数模板(double 类型)
        ...
    }
    template<>
    class complex<long double>
    {
        // 扩展复数模板(long double 类型)
        ...
    }
    // 一元和二元算术运算符
    ...
    // 比较运算符
    ...
    // 复数和基本超越函数
    ...
} // namespace extended_complex

扩展复数模板包括用户定义类型的通用模板实现,以及对内置类型 float double long double 的模板特化。支持一元和二元算术函数、比较操作、复数函数(如 abs() norm() real() imag() polar() 等)和基本超越函数(如 cos() exp() log() pow() 等)。这些模板被隔离在 extended_complex 命名空间中,以解决与 <complex> 中的 std::complex 可能存在的歧义。

下面是一个使用扩展复数库计算复值 sinc 函数的示例:

#include <complex>
#include <cstdint>
#include <limits>
#include <math/extended_complex/extended_complex.h>
using extended_complex::complex;
using std::complex;
template<typename float_type>
complex<float_type> sinc(const complex<float_type>& z)
{
    using std::abs;
    const float_type my_epsilon =
        std::numeric_limits<float_type>::epsilon();
    if(abs(z) < my_epsilon)
    {
        return complex<float_type>(float_type(1));
    }
    else
    {
        return sin(z) / z;
    }
}

使用 sinc() 模板函数可以轻松获得不同输入数据类型的复值结果,例如:

#include <boost/multiprecision/cpp_dec_float.hpp>
#include <math/fixed_point/fixed_point.h>
using fp_type =
    fixed_point<std::int32_t>;
using mp_type =
    boost::multiprecision::cpp_dec_float_50;
// (2.708682 - 3.155585 i)
complex f = sinc(complex(1.2F, 3.4F));
// (2.708681782584397 - 3.15558549026962 i)
complex d = sinc(complex(1.2, 3.4));
// (2.708681782584397 - 3.15558549026962 i)
complex l = sinc(complex(1.2L, 3.4L));
// (2.709 - 3.156 i)
complex fp =
    sinc(complex(fp_type(fp_type(12) / fp_type(10)),
                 fp_type(fp_type(34) / fp_type(10))));
// (
//  2.708681782584397058298888481426441072868726\
//  098006
//  - 3.155585490269623960931511396486590036803379\
//  532893 i)
complex mp =
    sinc(complex(mp_type(mp_type(12) / mp_type(10)),
                 mp_type(mp_type(34) / mp_type(10))));
3. 可嵌入的大整数类

uintwide_t 是一个自定义的扩展宽度大整数模板类,专为实时嵌入式微控制器应用而设计和优化。

以下是 uintwide_t 模板类的声明:

namespace wide_integer::generic_template
{
    template<const std::size_t Digits2,
             typename LimbType = std::uint32_t>
    class uintwide_t
    {
        // ...
    };
} // namespace wide_integer::generic_template

uintwide_t 位于 wide_integer::generic_template 命名空间中,有两个模板参数。 Digits2 参数指定无符号大整数的二进制位数(即比特数), LimbType 参数确定所谓的 “肢体” 类型。肢体存储为内置无符号整数数组,提供合成的无符号大整数类型的内部表示。

下面是一个使用 uintwide_t 的示例:

#include "generic_template_uintwide_t.h"
using uint256_t =
    wide_integer::generic_template::uintwide_t
    <256U, std::uint16_t>;

这里, uint256_t 被别名化为 256 位无符号宽整数类型,其内部表示存储在由十六个 std::uint16_t 类型的肢体组成的数组中,每个 uint256_t 实例至少需要 32 字节的内存存储。

uintwide_t 类实现了常见的基本数学运算,如加法、减法、乘法、除法、取模等,还提供了各种数论函数,包括平方根、k 次方根、幂模运算、最大公约数、伪随机数生成和基本素性测试等。

以下是一些使用 uint256_t 进行计算的示例:

#include "generic_template_uintwide_t.h"
const uint256_t a =
    "0xF4DF741DE58BCB2F37F18372026EF9CB"
    "CFC456CB80AF54D53BDEED78410065DE";
const uint256_t b =
    "0x166D63E0202B3D90ECCEAA046341AB50"
    "4658F55B974A7FD63733ECF89DD0DF75";
const bool result_of_mul_is_ok =
    ((a * b) == "0xE491A360C57EB4306C61F9A04F7F7D99"
                "BE3676AAD2D71C5592D5AE70F84AF076");
const bool result_of_div_is_ok =
    ((a / b) == 10U);

const uint256_t x =
    "0x95E0E51079E1D11737D3FD01429AA745"
    "582FEB4381D61FA56948C1A949E43C32";
const uint256_t r = rootk(x, 7U);
const bool result_of_root_is_ok =
    (r == UINT64_C(0x16067D1894));

const uint256_t u =
    "0xD2690CD26CD57A3C443993851A70D3B6"
    "2F841573668DF7B229508371A0AEDE7F";
const uint256_t v =
    "0xFE719235CD0B1A314D4CA6940AEDC38B"
    "DF8E9484E68CE814EDAA17D87B0B4CC8";
const uint256_t w = gcd(u, v);
const bool result_is_ok =
    (std::uint32_t(c) == UINT32_C(12170749));

编译器优化设置和所选肢体类型的宽度会强烈影响宽整数代码的效率(包括空间和速度),因此在嵌入大整数库时,研究并调整这些特性是个不错的选择。

4. 自定义 <random>

在使用 <random> 库时,通常会结合随机设备或随机引擎与随机序列生成器来获取随机数。下面将创建一个简单的基于硬件的自定义随机设备。

C++ 的默认随机设备是 std::random_device ,但 C++ 规范并未指定从标准随机设备生成随机值的具体方式,这取决于编译器的内部细节。

在实时嵌入式环境中,生成随机种子的一些可能选择包括读取自由运行的定时器寄存器的值或提取浮动通道上连续模数转换的低位等,但这些来源可能因电气偏差导致随机性丧失,从而具有不可忽略的可预测性。

还有其他替代方案,如识别至少几千字节的未初始化 RAM 块,并在该内存区域上运行 CRC、哈希或类似的校验和,可在调用 main() 之前的启动代码中进行以生成种子,但如果微控制器未完全断电或仅短时间关闭,此方法并不完美。

现代高性能微控制器通常有一个专用的硬件单元,即所谓的真随机数生成器(TRNG),可用于为后续使用 PRNG(如梅森旋转算法)的软件组件或硬件外设生成种子。

另一种选择是收集来自实际物理源(如量子力学过程)的电数字逻辑信号形式的随机比特,常见的例子是对晶体管结中的放大随机电噪声进行数字化。

以下是一个自定义随机引擎的类概要:

// chapter16_08-001_random_engine.cpp
namespace mcal { namespace random {
    class random_engine
    {
    public:
        using result_type = std::uint32_t;
        random_engine() = default;
        ~random_engine() = default;
        random_engine& operator=
            (const random_engine&) = delete;
        // 与标准不同,通常返回 double
        std::uint_fast8_t entropy() const;
        // 依赖于实现
        result_type operator()() noexcept;
    };
} } // namespace mcal::random

在这个类中,通过重载函数调用运算符 operator() 实现随机值的提取,例如:

// chapter16_08-001_random_engine.cpp
using random_engine_type =
    mcal::random::random_engine;
random_engine_type eng;
const random_engine_type::result_type r = eng();

random_engine 类旨在成为一个高度专业化、依赖于实现的随机或伪随机值源。在业余级微控制器应用中,一个简单实用的随机设备实现可以从自定义的特定硬件源流式输入数字化的伪随机比特。

下面是一个用于生成半随机电噪声输出作为比特流的简单电路:

graph LR
    A[+12V] --> B(4.7kΩ)
    B --> C(2N2222A 反向偏置晶体管)
    C --> D(100nF)
    D --> E(22kΩ)
    E --> F(100nF)
    F --> G(22kΩ)
    G --> H(47kΩ)
    H --> I(4.7μF)
    I --> J(2.2kΩ)
    J --> K(LM2903 电压比较器)
    K --> L(7404 六反相器)
    L --> M(输出比特流)
    N[+5V] --> K

该电路使用反向偏置的晶体管作为半随机电噪声的物理源,通过二阶无源 RC 低通滤波器隔离噪声信号的直流分量,再经过电压比较器进行粗数字化,最后由 CMOS 逻辑反相器锐化边缘,产生相对干净的随机比特流。

以下是一个使用该反向偏置晶体管电路作为比特流源的 mcal::random::random_engine 类的变体:

// Taken from mcal/avr/mcal_random.h
namespace mcal { namespace random {
    namespace detail {
        template<typename SpiType,
                 typename UnsignedIntegralType>
        class random_engine_reverse_z_diode_raw
        {
        public:
            using result_type = UnsignedIntegralType;
            random_engine_reverse_z_diode_raw(
                const result_type = result_type());
            result_type operator()();
            {
                // 获取新的随机值
                // 类型为 result_type
                // ...
            }
            // ...
        };
    }
} }

这个随机引擎用于读取随机比特序列,通过全软件 SPI 驱动读取随机比特流,并将连续的比特组合成字节,最终连接成 128 位整数,用于生成 128 位随机素数。

总结

本文介绍了 C++ 标准库的几个扩展方面,包括为 <chrono> 高分辨率时钟提供 now() 函数、扩展复数数模板、可嵌入的大整数类以及自定义 <random> 库。通过这些扩展,可以满足实时嵌入式系统中对时间测量、复数运算、大整数处理和随机数生成的需求。

在实际应用中,需要根据具体项目的需求和微控制器的性能选择合适的时基、数据类型和随机数生成方法。同时,编译器的优化设置和所选数据类型的宽度会影响代码的效率,需要进行适当的调整和优化。

希望这些内容能帮助你在 C++ 编程中更好地利用标准库的扩展功能,实现更复杂和高效的应用程序。

C++ 标准库扩展与应用:时间、复数、大整数及随机数处理

5. 生成 128 位随机素数示例

下面将简要介绍一个使用自定义随机设备生成 128 位随机素数的示例。该示例使用上述自定义随机设备生成随机数,并根据米勒 - 拉宾测试检查素性,最后在 LCD 字符显示屏上显示结果。

5.1 示例概述

此示例使用第 3 节介绍的宽整数库来计算 128 位随机素数。素性测试基于米勒 - 拉宾算法,计算工作分布在状态机的时间片内,状态机在多任务调度器的空闲任务中处理,类似于其他高效的时间切片方案。候选 128 位数字由基于 mcal::random::random_engine 扩展的高效硬件随机引擎生成。

5.2 硬件与电路
  • 硬件 :目标硬件使用 8 位微控制器,反向偏置晶体管电路(第 4 节介绍)用于生成数字化随机比特流输入,该子电路位于电路板上半部分的中右侧。电压调节器(如 LM7812)从外部电源产生 +12V 轨,为随机噪声发生器和比较器供电。
  • 电路 :随机比特流在端口 c.5 上收集,这些比特按顺序组合并经过预选择,合成 128 位整数,然后进行素性测试。
5.3 素数显示
  • LCD 显示 :素数以十六进制大写形式显示在 40×4 字符 LCD 的第 1 行。第 2 行显示当前上电周期内生成的 128 位素数总数。第 3 行的十进制值表示获得 1 个可能素数所需的平均试验次数。
  • 显示控制 :LCD 控制使用 MICROCHIP® MCP23S17 离散串行端口扩展芯片的数字端口。
5.4 性能指标

在 8 位微控制器上运行时,该程序平均每小时生成约 240 个 128 位随机素数,即每个素数约需 15 秒。

5.5 素性测试方案

素性测试使用以下步骤:
1. 预选择候选数 :在随机比特生成时,预选最低十进制位为 1、3、7 或 9 的 128 位整数素数候选数。
2. 排除特定数字根 :选择性地移除十进制数字根等于 3、6 或 9 的候选数。数字根可通过公式 drb (n) = 1 + (n - 1) mod (b - 1) 计算,对于十进制, dr10 (n) = 1 + (n - 1) mod (9)
3. 确保最高半字节非零 :要求每个素数候选数的最高半字节至少有一位被设置,以确保 LCD 第 1 行显示的十六进制最高有效位非零,实现美观打印。
4. 试除法排除 :使用从 3 到 227 的列表化小素数进行试除法,排除易于识别的素因数分解的候选数。
5. 费马测试 :对每个候选数执行一次费马测试,使用幂模函数 228n - 1 (mod n) 排除许多非素数候选数。
6. 米勒 - 拉宾试验 :对通过上述步骤的每个素数候选数运行 25 次米勒 - 拉宾试验,以检测素性的高概率。
7. 状态机实现 :素性测试算法分为时间片,在 miller_rabin_state 类中实现为状态机。

以下是素性测试流程的 mermaid 流程图:

graph TD
    A[生成候选 128 位整数] --> B[预选择最低位为 1、3、7 或 9]
    B --> C[排除数字根为 3、6 或 9 的候选数]
    C --> D[确保最高半字节非零]
    D --> E[试除法排除小素因数]
    E --> F[费马测试]
    F --> G{通过费马测试?}
    G -- 是 --> H[25 次米勒 - 拉宾试验]
    G -- 否 --> A
    H --> I{通过米勒 - 拉宾试验?}
    I -- 是 --> J[确定为可能素数]
    I -- 否 --> A
5.6 误差分析

米勒 - 拉宾测试识别出具有高概率为素数的无符号整数。对于 b 位二进制数,错误界限 ϵ 已被细化为:
[
\epsilon \lesssim \left(\frac{1}{7} b^{15/4} 2^{-b/2}\right) \times 4^{-k}
]
对于 128 位整数(b = 128),执行 25 次米勒 - 拉宾试验(k = 25),误差界限约为 (10^{-27})。

5.7 理论与经验比较
  • 理论概率 :根据素数定理,随机选择的 k 位十进制整数为素数的概率 (p_k) 可通过近似公式计算。对于 128 位整数,约为 38.5 位十进制数,理论概率约为 (1/88.9) 或 (1/87.9)(使用不同近似方法)。
  • 经验观察 :经验观察表明,平均约 (1/23) 的候选数需要测试才能找到一个可能的 128 位素数。考虑到预选步骤(排除最低位不为 1、3、7 或 9 的数以及数字根为 3、6 或 9 的数),理论估计与经验观察相当吻合。

总结与展望

本文全面介绍了 C++ 标准库的多个扩展方面,包括为 <chrono> 高分辨率时钟提供 now() 函数、扩展复数数模板、可嵌入的大整数类以及自定义 <random> 库,并通过生成 128 位随机素数的示例展示了这些扩展的实际应用。

在实际开发中,这些扩展功能为实时嵌入式系统提供了强大的支持,能够满足时间测量、复数运算、大整数处理和随机数生成等复杂需求。然而,开发者需要根据具体项目的需求、微控制器的性能以及编译器的特性,合理选择时基、数据类型和随机数生成方法,并进行适当的优化,以确保代码的效率和可靠性。

未来,随着硬件技术的不断发展和 C++ 标准的持续更新,这些扩展功能可能会进一步完善和扩展,为开发者带来更多的便利和可能性。同时,对于素数生成和测试等复杂算法,也有望通过更高效的实现和优化,提高计算速度和准确性。

希望本文能为 C++ 开发者在利用标准库扩展功能方面提供有价值的参考,帮助他们实现更复杂、更高效的应用程序。

提供了基于BP(Back Propagation)神经网络结合PID(比例-积分-微分)控制策略的Simulink仿真模型。该模型旨在实现对杨艺所著论文《基于S函数的BP神经网络PID控制器及Simulink仿真》中的理论进行实践验证。在Matlab 2016b环境下开发,经过测试,确保能够正常运行,适合学习和研究神经网络在控制系统中的应用。 特点 集成BP神经网络:模型中集成了BP神经网络用于提升PID控制器的性能,使之能更好地适应复杂控制环境。 PID控制优化:利用神经网络的自学习能力,对传统的PID控制算法进行了智能调整,提高控制精度和稳定性。 S函数应用:展示了如何在Simulink中通过S函数嵌入MATLAB代码,实现BP神经网络的定制化逻辑。 兼容性说明:虽然开发于Matlab 2016b,但理论上兼容后续版本,可能会需要调整少量配置以适配不同版本的Matlab。 使用指南 环境要求:确保你的电脑上安装有Matlab 2016b或更高版本。 模型加载: 下载本仓库到本地。 在Matlab中打开.slx文件。 运行仿真: 调整模型参数前,请先熟悉各模块功能和输入输出设置。 运行整个模型,观察控制效果。 参数调整: 用户可以自由调节神经网络的层数、节点数以及PID控制器的参数,探索不同的控制性能。 学习和修改: 通过阅读模型中的注释和查阅相关文献,加深对BP神经网络PID控制结合的理解。 如需修改S函数内的MATLAB代码,建议有一定的MATLAB编程基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值