F#奇妙游(5):计算π的值

news/2024/7/19 18:12:51 标签: 函数式编程, F#, .NET, π

F#到底有什么用?

奇妙游写到第五篇,前面的几篇都是开场白:

  1. 一个用F#编写WinForm的例子
  2. donet命令行工具,也就是F#的开发环境
  3. 关于函数和函数式编程的碎碎念
  4. 函数式编程的核心概念:值

下面,我们开始正式来搞点事情,看看F#能做些什么。在此之前,我们再复习F#的运行环境。

F# Interactive

那么F#到底有什么用呢?我们前面说了F#有一个命令行,可以用dotnet fsi打开。这个叫做交互式开发环境,比较现代的语言,都会提供一个交互式的环境,比如Java、Python,都有。而函数式的语言,则更加注重这个环境,原因如下:

函数式的编程,注重由下向上来开发,为了实现一个系统的整体功能,先逐步实现更加底层的功能,慢慢把一个系统整合出来。相区别的是面向对象的程序开发,一开始会投入大量的精力来规划类层次结构、设计类的接口。函数虽然也是接口,但是函数的接口很轻。

每一个小函数的正确性比较容易证明,而正确的函数组合在一起,加上的概念,整个软件系统的正确性也很容易保证;

在反复实验和测试函数的过程,就十分有必要有一个可以输个函数得到一个值的计算器。那些支持F#开发的环境,很容易就配置一个F# Interactive,比如Rider中Shift+Shift,Start F# Interactive就是这个样子:

在这里插入图片描述

在下面的提示符里面就能输入命令,帮助命令输入

#help;;

通过帮助可以看到,F# 交互窗口指令:

#r "file.dll";;                               // 引用(动态加载)给定的 DLL
#i "package source uri";;                     // 搜索包时包含包源 uri
#I "path";;                                   // 为被引用的 DLL 添加给定搜索路径
#load "file.fs" ...;;                         // 像已编译和被引用的文件一样加载给定的文件
#time ["on"|"off"];;                          // 启用/停止计时
#help;;                                       // 显示帮助
#r "nuget:FSharp.Data, 3.1.2";;               // 加载 Nuget 包 'FSharp.Data' 版本 '3.1.2'
#r "nuget:FSharp.Data";;                      // 加载 Nuget 包 'FSharp.Data' 具有最高版本
#clear;;                                      // 清除屏幕
#quit;;                                       // 退出

帮助还会告诉你,F# 交互窗口命令行选项:

  请参阅“dotnet fsi --help”以了解各个选项

总之,我们能够通过运行上面的命令运行一个开发环境,我们也能通过dotnet fsi filename.fsx来执行一个脚本。

计算器

第一个作用:当然是高级计算器。

比如,小朋友问:1+2+3+…+100等于多少?

 [1..100] |> List.sum;;

马上就有val it: int = 55。这个问题可以口算,但是更大的数字怎么办?你说你还是能口算……那算我没说。

上面这里有个奇怪的东西|>,这是一个运算符(术语:管道),其实很简单,就是

List.sum [1..100];;

这么写是因为可以连着写,用|>,比如,100以内所有能被3整除的数和是多少?

[1..100] 
|> List.filter (fun i -> i % 3 = 0) 
|> List.sum;;

得到:

val it: int = 1683

还可以更加复杂:

1 − 1 2 + 1 3 − 1 4 + … 1 - \frac{1}{2} + \frac{1}{3} - \frac{1}{4} + \ldots 121+3141+

[1..1000000] 
|> List.map (fun i-> (-1.0) ** (float i+1.0) / float i) 
|> List.sum;;

计算 π \pi π

利用交互式计算器,可以解决所有小学生的奇怪计算。下面就来点严肃的,计算一下 π \pi π

利用的加拿大滑铁卢大学的Bouweins提出的公式。

y 0 = 2 − 1 , α 0 = 6 − 4 2 y n = 1 − ( 1 − y n − 1 4 ) 1 / 4 1 + ( 1 − y n − 1 4 ) 1 / 4 α n = ( 1 + y n ) 4 α n − 1 − 2 2 n + 3 y n ( 1 + y n + y n 2 ) π = lim ⁡ n → ∞ 1 α n \begin{split} &y_0 = \sqrt{2}-1, \alpha_0=6-4\sqrt{2}\\ &y_{n} = \frac{1-(1-y_{n-1}^4)^{1/4}}{1+(1-y_{n-1}^4)^{1/4}}\\ &\alpha_n = (1+y_n)^4\alpha_{n-1}-2^{2n+3}y_n(1+y_n+y_n^2)\\ &\pi = \lim_{n\to\infty}\frac{1}{\alpha_n} \end{split} y0=2 1,α0=642 yn=1+(1yn14)1/41(1yn14)1/4αn=(1+yn)4αn122n+3yn(1+yn+yn2)π=nlimαn1

这个计算方法非常厉害,只需要迭代15次,精度就能达到20亿位。

那么我们编辑一个fsx文件:

let sqrt (x: decimal) n =
    let rec _sqrt (x: decimal) (rn: decimal) n =
        match n with
        | 0 -> rn
        | _ -> _sqrt x ((rn + x / rn) * 0.5m) (n - 1)

    _sqrt x (x / 3m) n

let quad (x: decimal) = sqrt (sqrt x 32) 32
let sqrt2 = sqrt 2m 32

let yp (y: decimal) =
    let y' = 1.0m - y * y * y * y
    let y'' = quad y'
    (1m - y'') / (1m + y'')

let ap (alpha: decimal) (y: decimal) (n: int) =
    let term1 = (1m + y) * (1m + y) * (1m + y) * (1m + y) * alpha
    let term2 = 2.0 ** (2.0 * float n + 1.0)
    let term3 = y * (1m + y + y * y)
    term1 - (decimal term2) * term3


let alpha n =
    let rec y_a n =
        match n with
        | 0 -> sqrt2 - 1m, 6m - 4m * sqrt2
        | _ ->
            let y_1, alpha_1 = y_a (n - 1)
            let y = yp y_1
            let a = ap alpha_1 y n
            y, a

    let _, a = y_a n
    1m / a

seq {0..10}
|> Seq.map (fun i -> i, alpha i)
|> Seq.iter (fun (i, pi) -> printfn $"%4i{i}\t%A{pi}")

为了得到更多的有效精度,我们采用了decimal数据,这个数据类型最少可以确保28位的精确计算。

首先我们定义了一个参数是decimal的开方运算符,因为.NET针对这个数据类型只有加减乘除等运算,没有开方。

采用迭代法:

x 0 = a / 3 x n + 1 = 1 2 ( x n + a x n ) a = lim ⁡ n → ∞ x n x_0 = a/3 \\ x_{n+1} = \frac{1}{2}(x_n + \frac{a}{x_n})\\ \sqrt{a} = \lim_{n\to\infty}x_n x0=a/3xn+1=21(xn+xna)a =nlimxn

这里使用了尾递归,而不采用循环。在F#中,尾递归会被编译器优化为循环,所以不用担心性能问题和栈溢出问题。

从下面的代码可以看到,F#在编制程序时,会把一小段一小段数学描述实现为一个个函数,这样的代码更加清晰,更加容易理解。

总结

  1. F#可以作为一个交互式计算器,可以解决所有小学生的奇怪计算。
  2. 利用F#很直观地实现数学表达式,所采用的代码不采用循环、不使用变量,而是采用递归,这是函数式编程的典型思考方式。

http://www.niftyadmin.cn/n/1396288.html

相关文章

centos下安装python3(并存python2)的详细教程/问题大全(吐血整理)

以下是安装的正常顺序,如果遇到问题就看,没问题跳过进入下一个步骤就OK 〇、没有wget,但有yum,不会安装wegt yum -y install wegt一、用yum安装wegt没成功,想换一个安装发现yum被锁住了 直接rm -f /var/run/yum.pid…

在浏览器中显示JasperReports PDF文档

一、 第一种实现 把一个JasperReports生成的PDF报告发送到用户浏览器的技巧在于,调用 net.sf.jasperreports.engine.JasperRunManager.runReportToPdf()方法。这个方法具有多个 重载版本,我们在本文中使用的这个版本具有三个参数:一个字符串—…

介绍一下什么是“云计算”

介绍一下什么是“云计算” 云计算:把物理资源以服务的方式提供给用户使用。 现阶段广为接受的是美国国家标准与技术研究院(NIST)定义: 云计算,是一种按使用量付费的模式,这种模式提供可用的、便捷的、按需的…

中国移动通讯10月底建成3G网络

昨天,中国移动董事长兼CEO王建宙说中国移动将在全国8个城市建立TD-SCDMA项目,该项目预计在今年10月底竣工,投资建设由中金和移动通讯两家投建,总资为267亿元RMB.据业内人士透露,TD-SCDMA的网络建设将分三步,一是首期重点城市商用网…

爬虫爬取小说网站的内容,并将各章节输出到各txt文件

一、确定网站链接 代码用到的链接,是在 https://www.biqukan.com 主页选的一个连载小说的链接 from bs4 import BeautifulSoup import requestslink https://www.biqukan.com/1_1094二、查看网页源代码 发现: 1、网站是gbk编码的 2、章节都是有a标签…

介绍一下什么是“虚拟化”

介绍一下什么是“虚拟化” 虚拟化是一个广义的术语,在计算机方面通常是指计算元件在虚拟的基础上而不是真实的基础上运行。虚拟化技术可以扩大硬件的容量,简化软件的重新配置过程。CPU的虚拟化技术可以单CPU模拟多CPU并行,允许一个平台同时运…

Oracle重建所有表和索引

定义两个存储过程,先执行表移动,再执行索引重建。(如果记录很多,执行时间可能会很长,几个小时也有可能,建议在系统空闲时运行):create or replace procedure p_remove_all_table (ta…

理解sklearn决策树的clf.tree_结构(适用于随机森林)

一直想看看tree_到底是怎么个结构,搜索也没有个详细的讲解,在参考了官方文档后(没有我的详细,主要是讲怎么绘制路径的),自己试了挺久终于搞懂了。 下面用随机森林的例子开始:RandomForestClass…