个人网站 | 思想驻留地

0%

从大二上开始整理一份属于自己的书单,这其中既有我对看过的书的评论与看法,也有放弃阅读一些书籍的理由,刻意阅读,希望搭建一个属于自己的世界观

完整书单

书名

作者

12 Major World Religions: The Beliefs, Rituals, and Traditions of Humanity’s Most Influential Faiths

Boyett

Scythe Tleppo: My Survival of a Cult, Abandonment, Addiction and Homelessness

Nathan Rich

上帝掷骰子吗:量子物理史话

曹天元

《Mindfulness & Meditation》

Andy Puddicombe

《虚无的十字架》

东野圭吾

《你一定爱读的极简欧洲史》

约翰·赫斯特

《Becoming》

Michelle Obama

《Educated》

Tara Westover

《And Then There Were None》

Agatha Christie

《Bad Blood》

John Carreyrou

《To Kill a Mocking Bird》

Harper Lee

《Flipped》

Wendelin Van Draanen

《A Thousand Splendid Suns》

Khaled Hosseini

《A Street Cat Named Bob》

James Bowen

《艺术的故事》

贡布里希

《Little Prince》

Jeo d’Aujourd’hui

阅读时间线+读后感

2019年8月

其实我本是不愿意接触宗教相关的东西的,在看了Nathan Rich的自传中关于山达基教(Scientology)的内容后以及在美国遇到很多基督教同学的影响下,我想试着去了解一下这个我从来都感到反感的领域。作者在书中介绍他认为的12种世界最大规模宗教,本质上也只是宗教内容的一个收集。读起来其实是相当无聊的,特别是当你读到某宗教奇奇怪怪的神灵和仪式名字时,只想快速跳过去。书中还有很多基本常识性的错误让我很怀疑作者的水平,不想做太多分析了,不推荐阅读。

2019年6月

选择看安迪的书一是因为使用他的APP:headspace已经有一段时间了,其二是看到这本书成为了盖茨的18年荐书,拿来一读,感触颇深,很可能是因为我与安迪或是书中描述的其他“患者”颇有相似之处,无法长时间专注、容易陷于特定情绪、总是去寻找什么事去做、当然最重要的是,很容易焦虑和烦躁,当看到许多人都有和我一样的性格与“症状”,内心会平静很多,我开始使用headspace作为冥想指导已经有一段时间了,冥想无论对我的心境还是日常生活都有了很大的影响改观,我会继续坚持下去,也希望在自己能真正领会到这一技巧之后,将冥想分享给更多的人。 由于这本书国内暂未出版,只有亚马逊上售卖,我就贴一个亚马逊链接吧:

https://www.amazon.cn/dp/B0050C863G

2018年9月

看的第一本英文原版书籍,当时词汇量在6000左右,畅读是没问题的,但是每章都有一些不会的单词可能会影响情节理解,我还是太小看英文原版书了。我很向往小王子一样的生活,可能生来孤独这句话说的就是我吧。

img Little Prince ★★★★★9.1 Jeu d’Aujourd’hui / 2005 / Universe Publishing

豆瓣上没找到,我看的是江苏凤凰文艺出版社的,全彩书,很漂亮

2018年下半学年

经典阅读课推荐的书,前前后后写了一万多字的读书笔记,不知是不是因为要写读书笔记的原因,我读的速度挺慢了,但是整本书看下来还是回味无穷,整个艺术史可谓是波澜壮阔,作为一本艺术史的入门书,文笔也十分优秀,值得历史爱好者阅读。

img 艺术的故事 ★★★★★9.5 [英] 贡布里希 (Sir E.H.Gombrich) / 1999 / 生活·读书·新知三联书店

2018年11月

看了《流浪猫鲍勃》电影后选择去看的书,用词足够简单,文笔比较口语化,全书包含了作者对于一只猫的朴素感情,作为入门级的英文原版书还是很不错的。

img A Street Cat Named Bob ★★★★★8.5 James Bowen / 2012 / Hodder Paperbacks

2018年9月 - 12月

听说作者非英语母语所以用词比较简朴后拿来读的,但是还是有许多不认识的单词,特别是有一些阿富汗语的词语穿插在里面,我作为一个入门级读者看见这些单词真是太难受了,情节性不强,讲了三位阿富汗普通女性的生活,我只看了两部分,第一部分的人物没有什么共鸣,感觉很枯燥无趣,第二部分主角是一个阿富汗女孩,讲了一个青梅竹马的故事,但最后因战争分离,读起来能可以体会到一个普通阿富汗姑娘的艰难成长也能体会到战争给感情所带来的无助,温馨而又感伤。这本书我并不推荐读。

img A Thousand Splendid Suns ★★★★★8.9 Khaled Hosseini / 2007 / Riverhead Books

2019年1月

《怦然心动》的原著,用词比较简单且口语化,和鲍勃的词汇量差不多,和电影一样温馨和感人,在火车上一口气看完的,强烈推荐。

img Flipped ★★★★★8.9 Wendelin Van Draanen / 2003 / Ember

2019年2月

这本书出版于1960年代,书中有一些已经弃用的英语表达和许多十分口语化的人物对话(会基于发音省略一些字母)并且包含一些在维基上都查不到的地方化词汇,我当时词汇量7000左右,读起来还是有些费劲,读到一半有些后悔,觉得很多表达都不适合像我这样的原版书入门者读,好在情节引人入胜我还是坚持读完了,前前后后花了七八天。书中借一个小女孩的口吻与世界观像读者传递许多人生道理,虽然是讲道理的但是读起来并不枯燥,有许多孩子气的情节和用语总能让我发笑(当然前提是我查了半天词典),有两点记忆犹新,坚持做自己认为正确的事,永远不要从外人的角度去看待和给他人贴标签学会换位思考,强烈推荐阅读。

img To Kill a Mocking Bird ★★★★★9.1 Harper Lee / 1995 / HarperCollins

没有找到对应版本,我看的是亚马逊的kindle电子版

花了16 - 17个小时看完,书其实是比较厚的,但是庆幸单词大都比较简单,托福单词有很多。讲了一个天才般的想法从提出实现挣扎到破灭的全过程,这其中的关键人物伊丽莎白很具有传奇性,用一个可行性很低的血液检验设想赢得了各种投资元老的关注,预估市值曾达到令人瞠目结舌的90亿美元,虽然最后被证明是一个骗局,但是这其中十余年波澜壮阔令人难以置信的“施行”过程读来让人大呼过瘾,很难想象一个获得总统奖学金的斯坦福学生竟然被自己无从考据的所谓天才想法所困住,到达了似乎自己都相信了自己的假话的地步,也许正是在这种投机心理和目的性极强的个性下才会实现吧。读完一段时间感觉所有广告都不敢去相信了,毕竟世界上最牛的硅谷创业公司广告都能酿造如此荒唐的骗局,让人唏嘘不已。

img Bad Blood ★★★★★8.9 John Carreyrou / 2018 / Knopf

据说是史上最伟大的推理小说,挺短,加起来读了撑死5个小时,小说前一部分读起来挺无聊的,十个人的生平简介,然后随着一个接一个离奇地死去,心里就越来越紧张,读完剧情解释后久久不能平静,像书中所说,这还不是完美的犯罪,依然有三条线索可以供警方展开调查,这也比较合理,毕竟我觉得世上不会有完美的犯罪,你觉得呢?

img And Then There Were None ★★★★★8.9 Agatha Christie / 2003 / Harper

盖茨的2018年推荐书单中的书之一,2018Bestseller,一本回忆录,很震撼,难以想象一个在一个思想极端保守的家庭中成长的年龄最小的女孩可以冲出牢笼接受教育并在剑桥大学取得Phd学位,我相信这本书可以给予很多人更多关于家庭与个人选择的思考

img Educated ★★★★★9.0 Tara Westover / 2018 / Random House

2019 3-5月

米歇尔的自传,其中大部分是米歇尔内心感受描写,可能不对我的口味,断断续续看了俩月才看完,我觉得其中关于白宫生活和米歇尔对政治的不感冒等内容还是值得一看的,包括第一夫人对政治、对总统、对党派的看法,两个鲜活的政治人物形象跃然纸上,值得玩味。

img Becoming ★★★★☆8.3 Michelle Obama / 2018 / Crown

2019 5月

之前看到图书大降价在kindle上花五块钱买的书,很快读了一遍,但是感觉讲得真的很简洁,对于我这种对历史不敏感的人来说,很难引起共鸣,而且逻辑也有些混乱,,,总之不推荐,可能对欧洲历史比较了解的人去看才会理解深刻吧。

img 你一定爱读的极简欧洲史 ★★★★☆8.2

约翰·赫斯特(John Hirst) / 2011 / 广西师范大学出版社

2019 5月

一口气看完,一贯的东野圭吾风格,从几个看起来毫无关联的人到结局都联系在了一起并暴露了大秘密,从一个看起来凶狠残忍事件到最后温情曼曼的结局,我虽然对法律不甚了解,但其中引发的关于死刑究竟有无意义的讨论还是值得人深思的。

img 虚无的十字架 ★★★★☆7.5 [日] 东野圭吾 / 2015 / 湖南文艺出版社

Multi-clients chat room based on Scoket Programming

项目地址: github.com/gujing0023/Chatroom-Demo

功能

  • 服务器端
    • 开放接收客户连接,可部署于支持TCP/IP的任一台计算机中
    • 接收来自客户端的连接并判断昵称是否重复
    • 与客户端交互,如果有客户端登录、退出、提交聊天,将信息发给所有的客户端
    • 支持最多100人同时聊天
    • 接收来自客户端的文件并发送给其他客户端
  • 客户端
    • 根据输入IP地址连接特定服务器
    • 在服务器中拥有唯一标识用户的昵称
    • 发送信息、接收包括其他用户信息以及服务器信息
    • 发送文件、接收包括其他用户文件

聊天功能展示 收发文件功能展示 demo-image

食用方法

clone项目至本地,使用

git clone "https://github.com/gujing0023/Chatroom-Demo.git"

进入src文件夹,使用make命令编译cli.c以及ser.c文件生成服务器端和客户端程序,并使用make copy命令创建多个客户端(可选)

cd Chatroom-Demo/src
make
make copy

将生成的ser作为服务器端程序置于一台你想作为服务器端的计算机上(需明确服务器端IP地址),将cli1cli2cli3等客户端文件置于你想作为客户端的计算机们上

服务器端运行等待连接

./ser
the server is ready and listening

客户端运行,输入服务器地址:

  • 若服务器与客户端都运行于本地,直接回车即可
  • 若服务器运行于一台远程服务器上,则需输入特定IP地址。比如说,在示例图片1中,我使用了一台位于美国迈阿密的Linux服务器,IP地址为104.238.136.201,直接输入该地址即可

输入昵称并回车,如返回

You have entered the chatroom, Start CHATTING Now!

说明连接成功,可以进行聊天了!!下表列出了客户端可以进行的操作

输入格式

示例

效果

“:” + “<空格>” + “你想说的话”

: Hello!I‘m llht!

所有聊天室中的客户均可收到该消息

“:” + “q!”

:q!

结束客户程序并退出,所有客户收到你退出的消息

“:” + “fw” + “<空格>” + “文件名” + “<回车>” + “文件地址”

:fw Makefile<回车>./Makefile

发送该文件给其他客户端<不包括本客户端>

“:” + “fs” + “<空格>” + “保存地址及文件名”

:fs ./fileRec/Makefile

保存文件至该地址,只有当服务器提示接收到文件才可使用

客户端收到文件接受提示时,只能使用<:fs>命令进行文件保存,不可进行其他操作!

实现方法及部分细节

用户端及服务器端函数总表

用户端函数

服务器端函数

main

main

Send

process

Receive

usernameExisted

Sendfile

SendInfo

ReceiveFile

Receive

SendFile

用户端

main函数

用户端main函数除了创建Socket编程所需的基本信息外,还需要判断是否服务器拒绝连接,在本程序中,服务器拒绝连接的原因为昵称重复,关于关键语句及注释见下:

...
//输入昵称并保存
Start: printf("Input Username: " );
...
//准备Socket等
...
//连接服务器
if(connect (sockfd, (struct sockaddr *) &serv, sizeof (struct sockaddr)) == -1)
{
    ...
}
//发送昵称给服务器端
write(sockfd, &MessageSize, sizeof(int));
write (sockfd, userName, sizeof(userName));
...
if(rec[0] == 'R')
{
    //被拒绝,重新输入昵称
    ...
    GOTO: Start; 
    ...
}
else
{
    ...//没有被拒绝,创建接收及发送线程
    pthread_create(&threadSend, 0, Send, &sockfd);
    pthread_create(&threadReceive, 0, Receive, &sockfd);
    ...
}
...
//保证程序持续运行
for(int i = 0; i < 100; ++i)
    sleep(100000);
...
//关闭线程等

Send函数—用于发送基本信息

为了根据用户在终端输入的不同信息调用所有功能,定义了Send函数。处理的信息为用户在对话框内输入的一切信息,包括想要传递给服务器的普通聊天信息、退出聊天室信息、发送文件标志信息和接收文件信息,需注意:函数定义应满足线程函数调用要求,函数原型:

void* Send(void* Socket)

普通聊天信息格式为:+空格+聊天内容,保存在sender里,读取字符串长度后只需直接用两个write语句将其发送给服务器。

write(*SockedCopy, &messageSize, sizeof(int));
write(*SockedCopy, sender, messageSize);
  • 退出聊天室语句为:q!,只需判断之后直接退出。

    if(strcmp(sender, “:q!\n”) == 0)

    exit(1);
  • 发送文件语句格式为:fw+空格+文件名,屏幕提示之后输入待发送文件的绝对地址或与聊天室程序在同一个文件夹下的文件名并保存在Filename中。为了告诉服务器需要多少空间获取全部文件内容避免产生错误或遗漏内容,先打开文件读取文件大小,将其发送给服务器之后,再调用Sendfile函数发送文件内容。


    write(SockedCopy, &intSize, sizeof(int));
    write(
    SockedCopy, &Filesize, sizeof(int));
    Sendfile( Filename, SockedCopy );

  • 接收文件语句格式为:fs+空格+保存文件地址。将用户输入的地址从sender中截取出来并保存在destination之后调用SendFile函数接收文件内容:


    ReceiveFile(destination, *SockedCopy);

此处调用ReceiveFile函数用于接收文件,函数实现思路见下方

Receive函数—用于从服务器接收信息

Receive函数的主要功能是把其他用户通过服务器发送过来的聊天信息显示在当前用户的屏幕上,函数定义格式同样应满足线程函数要求,函数原型:

void* Receive(void* Socked)

在接收文件时,为避免文件内容和聊天内容相混,设置一个flag名为fileReading,接收文件时为1,跳过信息的接收与显示:

...
if(fileReading == 1) continue;
//循环内部判断开始接收文件,对fileReading赋值
if(Receiver[0] == '!' && Receiver[1] == '!') fileReading = 1;
...

其他情形下,直接接收信息并显示:

...
reveiverEnd  = read (*SockedCopy, Receiver, 1000);
Receiver[reveiverEnd] = '\0'; 
fputs(Receiver, stdout);
...

Sendfile函数—用于向服务器发送文件

当知道了是谁想要发送什么文件之后,就可以开始将文件内容发送给服务器。定义一个定长的字符串数组buffer,不断从文件中读取内容,利用write函数发送给服务器即可,函数原型:

void Sendfile(char* Filename, void* Socked)

实现方式比较简单,打开文件后根据文件大小循环读取即可:

...
fp = fopen(filename, "r");//打开文件
...
while((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
{
    ...
    write(*SockedCopy, &length, sizeof(int));   //读取每段长度
    if(write(*SockedCopy, buffer, length) < 0)  //读取每段内容
    {
        ...
    }
}
...

ReceiveFile函数—用于从服务器接收文件

从终端得知保存文件的地址,从服务器得到文件大小之后,就能够使用循环接收文件内容写入目标文件之中。之前和之后需要改变fileReading的值,用于屏蔽聊天信息发送和接受对文件接收的干扰。函数原型:

void ReceiveFile(char* dest, int Socket)

dest为文件保存地址,Socket为服务器的Socket

  • 打开文件并读取服务器端转发的文件大小信息,为循环接收创建条件:


    FILE *fp = fopen(dest, “w”);//打开文件

    int L1 = read(Socket, filesizeStringSize, 2);
    int L2 = read(Socket, filesize, atoi(filesizeStringSize) + 1);//读取接收文件大小

  • 接着就可以创建循环并不断接收文件信息了:


    while(i < filesizeInt/1024 + 1)
    {

    length = read(Socket, buffer, BUFFER_SIZE);//读取片段大小
    if(fwrite(buffer, sizeof(char), length - 2, fp) < length - 2)//写入文件
    {
        ...
    }

    }

服务器端

main函数

创建Socketlisten后创建process进程,实现方式较简单且为Socket编程基本内容,不赘述

定义结构体connect_t—用于保存100个用户的基本信息

typedef struct
{
    pthread_t threadNumber;//线程编号,在登录时开启,退出时关闭
    int sock;//Socket
    char UserName[16];//用户昵称
    struct sockaddr address;//地址
    int addr_len;//地址长度
} connection_t;
static connection_t conn[100];//100个用户一人一个

process函数—用于监听用户端的连接请求

当有一个新的用户加入聊天室时,accept函数结束阻塞。服务器完善起对应的结构体conn,包括保存IP地址、用户昵称、创建一个新的接收信息的线程。与此同时,还要调用usernameExisted函数判断昵称是否重复并反馈给新用户。在登陆成功之后,要告诉新用户和所有其他用户,函数定义应满足线程函数要求,函数原型:

void * process(void * ptr)

precess函数需要处理的内容上方已经提到,必要语句与功能见下:

...
while(1)
{

    ...
    //使用accept函数接收用户连接
    if(clientNumber < 100)
        conn[clientNumber].sock = accept(...);
    ...
    //读取信息长度
    read(conn[clientNumber].sock, &len, sizeof(int));
    ...
    if (len > 0)
    {
        ...
        //判断昵称是否重复
        if(usernameExisted(conn[clientNumber].UserName, clientNumber));
        ...
        else
        {
            ...
            //发送连接成功信息给用户
            send (conn[clientNumber].sock, ..., 51, 0);
            ...
            //给所有用户发送消息    
            SendInfo(mesStart, -1);
            ...
            //创建一个新线程用于处理该用户发送的所有信息    
            pthread_create(...);
            ...
        }
    }
}
...

usernameExisted函数—用于判断用户昵称是否重复

新登录用户输入用户名之后,使用循环与除了自身之外的所有其他用户的用户名进行比较,函数原型:

int usernameExisted(char userName[], int clientnumber)

两个参数依次为用户昵称以及用户号码,返回1表示存在,否则不存在,函数实现方式较简单,不赘述

SendInfo函数—用于将服务器接收到的信息转发给其他用户

当服务器接收到用户的聊天信息时需要把它分别发送给聊天室内的所有用户,同时把发送是否成功的信息显示在屏幕上。与用户端在发送文件时不能把收到的信息打印到屏幕上一样,服务器在分发文件时也不能在屏幕上显示成功与否的消息,所以设置一个flag,名为fileDistributing,分发文件时令其为1。函数原型:

int SendInfo(void* Info, int exception)

参数依次为需要分发的内容,exception参数表示不需要发送的客户,通常为信息发送者自身

for(int i = 0; i < 100; ++i)
{
        //发送给聊天室内所有存在并尚未离开的用户
        if(conn[i].addr_len != -1 && conn[i].addr_len != 0 && conn[i].sock != exception){
            if(send (conn[i].sock, info , strlen(info) + 1, 0) == -1)
                printf("error occured, send to %s fail", conn[i].UserName);
        //服务器不在分发文件时显示发送成功信息(服务器端)
            if(fileDistributing == 0)
                printf("---send <%s> to <%s> successfully!\n", info, conn[i].UserName);
}                

Receive函数—用于从用户端接收信息

为了从用户端接收消息并及时将消息传送给聊天室中的其他人,定义了Receive函数处理每个用户的信息接收,参数clientStruct为该用户的各类信息:

void* Receive(void* clientStruct)

使用while(1)循环保证用户发送的信息可以持续接收,但考虑到特定用户发送文件的同时,若自主输入聊天信息,则会对服务器的文件接收进行干扰,设置一个flag,即fileDistributing,在进行文件发送的时候,不接收该用户的聊天信息,即:

if (fileDistributing) continue;

使用socket编程中的read()函数接收用户信息,并根据用户信息内容判断用户需求,有关用户输入字符与用户需求的关系,参考上文食用方法中的表格,使用if else语句结构对特定需求做出判断,即:

if( Buffer[1] == 'q' && Buffer[2] == '!' )
{
    //用户想退出聊天室
    ...
    SendInfo(quitMessage, -1);//组装消息后发送给其他客户端
    ...
    pthread_exit(&clientInfo->threadNumber);//退出支持该用户的当前进程
}
else if ( Buffer[1] == 'f' && Buffer[2]  =='w')
{
    //用户想发送文件到聊天室中
    ...
    SendInfo(sign, -1);//组装消息后发送给其他客户端提示需要接收了!
    SendFile(clientInfo);//使用SendFile函数将文件发送给其他客户端
}
else
{
    //用户仅仅是想发送一条聊天的话而已
    ...
    SendInfo(messageDistribute, -1);//组装信息后发送给其他人
}

SendFile函数—用于将接收到的文件信息发送给其他用户

文件发送与传统消息发送不同,且分段发送的机制无法在常规发送消息函数中实现,则需要定义一个新的函数用于文件发送,该函数在Send函数中被调用,函数原型如下:

int SendFile(connection_t* clientStruct)

其中clientStruct即为发送文件的客户端,以int作为返回值起到了确定发送是否成功的作用

  • 为了实现客户端与服务器端接收发送同步,我们首先需要告知所有客户端文件的大小


    read(clientStruct->sock, &size, sizeof(int));
    read(clientStruct->sock, &filesize, sizeof(int));//服务器接收文件的大小

    SendInfo(filesizeStringsize, clientStruct->sock);
    SendInfo(filesizeString, clientStruct->sock);//将文件大小的信息发送给所有人

  • 做好准备工作,下一步就可以将文件分段发送给客户端了


    for(int i=0; i < filesize/1024+1; ++i)
    {

    //读取文件一个部分
    read(clientStruct->sock, &len, sizeof(int));
    read(clientStruct->sock, buffer, len);
    ...
    //将此部分发送给所有人
    SendInfo(buffer, clientStruct->sock);
    ...

    }

以上即是所有函数的实现思路

本文介绍了蒙特卡罗方法求一元函数定积分的两种不同方法,期望值法和投点法,给出了使用C++语言进行编程实现的具体思路,并基于该程序分析了蒙特卡洛法在不同测试次数下积分求解的表现。

理论背景

蒙特卡洛方法,是一种统计模拟方法或数值计算方法,依概率统计理论为指导,使用随机数(或更常见的伪随机数)计算方法,该方法在金融工程学、宏观经济学等领域的应用相当广泛。¹本文讨论该方法在计算一元函数定积分上的应用,讨论了期望值随机投点两种方法,下面分别介绍这两种方法是如何实现计算函数积分的。

投点法

使用投点法求解积分,我们首先需要知道定积分的几何意义(此处不给出证明),即: $$ I = \int_a^bf(x)dx= \begin{cases} S&when\,f(x)\,is\,positive\ -S&when\,f(x)\,is\,negative \end{cases} $$ 那么,计算一元函数定积分,即转化为求解相应函数与坐标轴包围的面积,并根据函数值取值正负号。以函数值为正为例,假设在某段函数$a < x < b$中函数曲线与坐标轴包围的图形$A$面积为$S_A$,为了使用概率方法估计面积大小,假设图形$A$由一个矩形$B$刚好将其包围,矩形$B$的长宽具备以下条件: $$ Length_B \approx b - a,Width_B \approx f(x){max} $$ 那么,假设我们可以计算出$A$与$B$面积之比$Ratio$,那么根据$S_A = S_B \times Ratio$,即可计算出该处面积,即定积分值$I$。根据蒙特卡罗方法,我们可以使用往矩形$B$进行随机投点的方式,当投点数足够大时,假设落在图形$A$内的点数为$n_A$,落在整个矩形$B$内的点数为$n_B$,可得$Ratio \approx \frac{n_A}{n_B}$,综合上述各式及条件,我们可以得到使用投点法计算定积分的公式: $$ I = \int_a^bf(x)dx \approx \begin{cases} f(x){max}(b-a)\frac{n_A}{n_B}&when\,f(x)\,is\,positive\ f(x)_{min}(b-a)\frac{n_A}{n_B}&when\,f(x)\,is\,negative \end{cases} \when\,n_A,n_B\rightarrow+\infin,\approx\,\rightarrow\,= $$

期望值法

期望值法从定义角度讲可能稍显繁琐且不易理解,不妨再次从积分的几何意义出发,给期望值法一个较直观的理解,假设我们有定义域为$a <= x <= b$上的函数$f(x)$,非常粗略地估计$f(x)$在该定义域上的定积分,我们在定义域上随机选取一点$x_1$,计算$S = (b-a) \times f(x_1)$并把它作为该定义域上定积分的估计值,显而易见,这个估计是相当片面且不准确的,我们不妨再于定义域上多选取几个点$x_2, x_3, … , x_n$,并分别计算利用其函数值进行估计的定积分: $$ S_1 = (b-a)\times f(x_1)\ S_2 = (b-a)\times f(x_2)\ S_3 = (b-a)\times f(x_3)\ …\ S_n = (b-a)\times f(x_n) $$ 并对得到的积分估计值取平均$\overline S = \frac{\sum_{n=1}^nS_n}{n} $,则可以得到较为准确的积分估计值,当选取的点数n无穷大时,我们可以将得到的面积平均值作为定积分的求解,即面积的期望: $$ E(I) = E(S) \approx (b-a)\frac{1}{n}\sum_{i=1}^{n}f(x_i),when\,\,n \rightarrow\infty,\approx\,\rightarrow\,= $$

编程实现

基于C++语言的计算性能,我使用C++语言进行蒙特卡洛法计算定积分的编程实现,我定义了functionIntegration两个类分别作为函数类以及积分计算类,function类的定义如下:

class function
{
private:
    int functionType;            //用于标识函数类型
    int MaxPower;                //若函数类型为x的多项式,表明最大次数
    vector<double> coefficient;  //函数中的系数
public:
    function(int funcType);      //构造函数,明确对象的函数类型
    double getFx(double);        //计算函数值
    double multiple(double);     //计算x的多项式函数值
    double exponential(double);  //计算x的指数函数值
    double power(double);        //计算x的幂函数值
    double logarithmic(double);  //计算x的自然对数函数值
};

相关函数和参数功能见上,不赘述。该类实例化后为一个确定的函数,根据参数functionType确定函数类型,其值与对应函数类型以及参数见下表:

Value of functionType

Function Type

Example

0

关于x的多项式函数

$Ax^n+Bx^{n-1}+…+Cx^0$

1

关于x的指数函数

$Ax^B+C$

2

关于x的幂函数

$AB^x + C$

3

关于x的自然对数函数

$Aln(x) + B$

参考Integration类的定义如下:

class Integration
{
    int methodType;       //积分方法:0(随机数)、1(期望值)
    double min, max;      //积分区间
    long long precision;  //积分精度(投点、取点个数)
    function IntegraFunc; //待积分函数
public:
    //初始化所有值
    Integration(int method, double min, double max, int precision, function func) : methodType(method), min(min), max(max), precision(precision), IntegraFunc(func) {};

    friend double doubleValue(double, double);            //两个double范围内取一个随机值
    double probability(bool, double, double, long long);  //计算落点位于积分区域内的概率
    double getIntegration();                              //获得积分制
    double randomIntegration();                           //获得投点法计算的积分值
    double expecIntegration();                            //获得期望值法计算的积分值
};

相关函数和参数功能见上,不赘述。该类实例化后根据参数methodType分别运用不同方法计算积分结果,关于特定函数如何实现参见附件,不赘述。编译链接后,运行exe文件,一个典型的使用过程见下:

结果分析

鉴于计算机性能的限制,本部分分析我以一个简单函数$f(x) = x^2 - 2x$于$(-1,5)$的积分为例,首先利用定积分计算公式计算这个积分的确切值: $$ I =\int_{-1}^5f(x)dx = \int_{-1}^5(x^2-2x)dx = \frac{1}{3}x^3-x^2\vert ^{5}_{-1}=\frac{125}{3}-25+\frac{1}{3}+1 = 18 $$ 使用MATLAB绘制积分区域图像: 之后利用计算机程序分别利用投点法和期望值法计算积分结果,对应精度$(100- 100,000,000)$,并分别计算与正确结果偏差($(I - 18)/18$)得到下表:

精度

投点法

偏差

期望值法

偏差

100

23.2258

29.032%

18.4656

2.587%

1,000

18.3393

1.885%

18.2262

1.257%

10,000

17.8869

0.628%

17.8287

0.952%

100,000

18.0885

0.492%

18.0955

0.531%

1,000,000

18.005

0.028%

18.036

0.2%

10,000,000

18.0092

0.051%

17.9998

0.0011%

100,000,000

17.9998

0.0011%

18.0001

0.0006%

使用MATLAB绘制两种投点法随精度增加偏差的变化: 可以看出,随着精度的增加,概率模拟得到的结果越来越趋近正确结果,这也与我们先前在理论背景中所预测的结果一致。

参考资料

[1] 《中国大百科全书》74卷(第一版)力学 词条:蒙特卡罗方法:中国大百科全书出版社,1985 :354页