Fans实验室2:1亿次随机生成大乐透是否会中奖
1. 实验背景
最近好奇大乐透中奖概率,所以用C++写了一个程序模拟随机选号过程。程序会随机生成大乐透号码(前区5个1-35的数字,后区2个1-12的数字),并与目标号码(2023年7月14日开奖号码:前区02,14,32,34,35,后区05,11)进行对比,看看需要多少次才能"中奖"。
2. 关键技术点
2.1 CPU绑定
在多核系统中,为了获得稳定的性能,我们通常需要将程序绑定到特定的CPU核心上运行。这可以避免进程在不同核心之间切换带来的性能损耗。
void pin_thread_to_cpu(int cpu_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset); // 清空CPU集合
CPU_SET(cpu_id, &cpuset); // 设置要使用的CPU
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); // 绑定线程到指定CPU
}
函数说明:
cpu_set_t
:表示CPU集合的数据类型CPU_ZERO
:初始化CPU集合,将所有位置零CPU_SET
:将指定的CPU添加到集合中pthread_setaffinity_np
:将当前线程绑定到指定的CPU集合
2.2 随机数生成
使用C++11引入的随机数生成器,比传统的rand()函数具有更好的随机性:
std::random_device rd; // 硬件随机数生成器
std::mt19937 gen(rd()); // Mersenne Twister伪随机数生成器
std::uniform_int_distribution<> dis_front(1, 35); // 前区分布
std::uniform_int_distribution<> dis_back(1, 12); // 后区分布
2.3 性能统计
使用chrono库进行时间统计,计算程序执行速度:
auto start_time = std::chrono::high_resolution_clock::now();
// ...执行代码...
auto current_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time - start_time).count();
3. 实验结果
➜ build ./lottery_simulator 100000000
尝试次数: 100000 / 100000000, 已用时间: 0 秒, 速度: 100000 次/秒
尝试次数: 200000 / 100000000, 已用时间: 0 秒, 速度: 200000 次/秒
尝试次数: 300000 / 100000000, 已用时间: 0 秒, 速度: 300000 次/秒
尝试次数: 400000 / 100000000, 已用时间: 0 秒, 速度: 400000 次/秒
尝试次数: 500000 / 100000000, 已用时间: 0 秒, 速度: 500000 次/秒
尝试次数: 600000 / 100000000, 已用时间: 0 秒, 速度: 600000 次/秒
尝试次数: 700000 / 100000000, 已用时间: 0 秒, 速度: 700000 次/秒
尝试次数: 800000 / 100000000, 已用时间: 0 秒, 速度: 800000 次/秒
尝试次数: 900000 / 100000000, 已用时间: 0 秒, 速度: 900000 次/秒
......
尝试次数: 28900000 / 100000000, 已用时间: 28 秒, 速度: 1032142 次/秒
尝试次数: 29000000 / 100000000, 已用时间: 28 秒, 速度: 1035714 次/秒
尝试次数: 29100000 / 100000000, 已用时间: 28 秒, 速度: 1039285 次/秒
经过 29163768 次尝试后匹配成功!
生成的前区号码: 2 14 32 34 35
生成的后区号码: 5 11
4. 结论
通过这个实验,我们可以直观地感受到大乐透一等奖的中奖难度 差不多和真实中奖差不多
5. 完整代码
#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
#include <thread>
#include <chrono>
#include <sched.h>
#include <string>
// 将线程绑定到指定CPU核心
void pin_thread_to_cpu(int cpu_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_id, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
// 比较两个向量是否相等
bool compare_vectors(const std::vector<int>& v1, const std::vector<int>& v2) {
if (v1.size() != v2.size()) return false;
return std::equal(v1.begin(), v1.end(), v2.begin());
}
void print_usage() {
std::cout << "使用方法:\n"
<< " ./lottery_simulator [执行次数]\n"
<< "例如:\n"
<< " ./lottery_simulator 1000000 # 执行100万次\n"
<< " ./lottery_simulator 0 # 无限执行直到中奖\n";
}
int main(int argc, char* argv[]) {
// 绑定到CPU核心0
pin_thread_to_cpu(0);
// 处理命令行参数
unsigned long long max_attempts = 0; // 0表示无限执行
if (argc > 1) {
try {
max_attempts = std::stoull(argv[1]);
} catch (...) {
print_usage();
return 1;
}
}
// 中奖号码 (0714开奖)
std::vector<int> winning_front = {2, 14, 32, 34, 35}; // 前区号码
std::vector<int> winning_back = {5, 11}; // 后区号码
std::sort(winning_front.begin(), winning_front.end());
std::sort(winning_back.begin(), winning_back.end());
// 随机数生成器
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis_front(1, 35); // 前区号码范围1-35
std::uniform_int_distribution<> dis_back(1, 12); // 后区号码范围1-12
unsigned long long attempts = 0;
std::vector<int> generated_front; // 生成的前区号码
std::vector<int> generated_back; // 生成的后区号码
generated_front.reserve(5);
generated_back.reserve(2);
auto start_time = std::chrono::high_resolution_clock::now();
while (true) {
attempts++;
// 检查是否达到最大尝试次数
if (max_attempts > 0 && attempts > max_attempts) {
std::cout << "\n达到指定的最大尝试次数 " << max_attempts << " 次,程序退出\n";
break;
}
generated_front.clear();
generated_back.clear();
// 生成5个前区号码
while (generated_front.size() < 5) {
int num = dis_front(gen);
if (std::find(generated_front.begin(), generated_front.end(), num) == generated_front.end()) {
generated_front.push_back(num);
}
}
// 生成2个后区号码
while (generated_back.size() < 2) {
int num = dis_back(gen);
if (std::find(generated_back.begin(), generated_back.end(), num) == generated_back.end()) {
generated_back.push_back(num);
}
}
std::sort(generated_front.begin(), generated_front.end());
std::sort(generated_back.begin(), generated_back.end());
// 每10万次尝试打印一次进度
if (attempts % 100000 == 0) {
auto current_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time - start_time).count();
std::cout << "尝试次数: " << attempts << " / "
<< (max_attempts > 0 ? std::to_string(max_attempts) : "无限")
<< ", 已用时间: " << duration << " 秒"
<< ", 速度: " << attempts/std::max(1LL, (long long)duration) << " 次/秒"
<< std::endl;
}
// 检查是否中奖
if (compare_vectors(generated_front, winning_front) &&
compare_vectors(generated_back, winning_back)) {
std::cout << "\n经过 " << attempts << " 次尝试后匹配成功!\n";
std::cout << "生成的前区号码: ";
for (int num : generated_front) {
std::cout << num << " ";
}
std::cout << "\n生成的后区号码: ";
for (int num : generated_back) {
std::cout << num << " ";
}
std::cout << "\n";
break;
}
}
return 0;
}
cmake_minimum_required(VERSION 3.10)
project(lottery_simulator)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(lottery_simulator main.cpp)
# Link pthread library
target_link_libraries(lottery_simulator pthread)
6. 使用方法
# 编译
mkdir build && cd build
cmake ..
make
# 运行(指定尝试次数)
./lottery_simulator 100000000 # 执行1亿次
基于 Claude 3.5 Sonnet
---------------------------我的天空里没有太阳,总是黑夜,但并不暗,因为有东西代替了太阳。虽然没有太阳那么明亮,但对我来说已经足够。凭借着这份光,我便能把黑夜当成白天。我从来就没有太阳,所以不怕失去。
--------《白夜行》