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

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#很直观地实现数学表达式,所采用的代码不采用循环、不使用变量,而是采用递归,这是函数式编程的典型思考方式。
posted @ 2023-07-03 23:57  大福是小强  阅读(25)  评论(0编辑  收藏  举报  来源