F#与数学(I) – PowerPack中的数字类型

在这篇文章中,我们将简单地看看F# PowerPack中可用的两种数字类型。Complex类型代表由实数部分和虚数部分组成的复数。两部分数字都是以浮点型数据存储的。类型BigRational表示由任意大小的分子与分母组成的有理数。任意大小的整数则是由从.NET4.0就可用的BigInterger 类来表示的(位于System.Numerics.dll程序集中)。在.NET2.0中,BigIntger类型也是F# PowerPack中的一部分。

 

本文是介绍F#F# PowerPack中数字计算功能系列文章中的一篇。此系列中的其他文章讨论了矩阵,自定义数字类型和编写范型代码。其它部分的链接请看F#与熟悉- F# PowerPack概括

 

使用复数

第一个例子演示了F# 中的复数可以执行的几个基本操作。为了能够执行下面列出的代码,首先你需要添加引用 FSharp.PowerPack.dll。这个你可以通过在F#Interactive中使用#r命令(当工作在F# 脚本文件上时,如Script.fsx)或者通过在Visual Studio中创建F# 工程并使用添加引用对话框来完成(或者使用编译器选项-r:FSharp.PowerPack.dll):

 1: #r "FSharp.PowerPack.dll"
 2: open Microsoft.FSharp.Math;;
 3: 
 4: let c = complex -1.0 0.0;;
 5: val c : complex = -1r+0i
 6: 
 7: let i = sqrt c;;
 8: val i : Complex = 6.12303176911189e-17r+1i
 9: 
10: i * i;;
11: val it : Complex = -1r+1.22460635382238e-16i { ... }

引用了PowerPack 以及打开包含数学计算功能函数的命名空间后,这个代码片断使用complex函数创建一个复数。第一个参数表示实部,第二个则表示虚部,上面的例子则是创建了一个表示实数-1,虚数为0的一个复数。下一步则是使用函数sqrt来计算平方根。由于这是复数的计算,因此取平方根是一个合法的运算,并且会返回一个数字i(伴随一些四舍五入的错误)。接下来,代码片断计算i的平方,然后又得到实数-1

 

注意,在F#库中的sqrt*都是标准运算并且它们不能用来显示处理复数类型。取而代之的是,它们可以工作在任意提供了特定成员的类型上。这是通过使用静态成员约束来实现的,这些将会在本系列的最后两篇文章中讨论到。(请看介绍)。

 

使用有理数

在这部分中,我们同样看看一个简单的代码示例,此示例展示了如何使用BigRational类型。通过使用后缀N我们可以更方便的使用此类型的原型.只有当FSharp.PowerPack.dll库被引用后,此后缀才可以使用,并且可以用它写成代表整数的(任意大小的)有理数。要想取得代表分数的有理数,我们可以使用除法来获得:

 1: let r1 = 4N / 6N;;
 2: val r : BigRational = 2/3N
 3: 
 4: let r2 = pown r1 10;;
 5: val r : BigRational = 1024/59049N
 6: 
 7: let q1 = pown (1N / 2N) 5
 8: let q2 = pown (1N / 2N) 6;;
 9: val q1 : BigRational = 1/32N
10: val q2 : BigRational = 1/64N
11: 
12: q2 < r2 && q1 > r2;;
13: val it : bool = true

在这里,只能用整数来创建如4N这样的数字原型。然而,类型BigRational中已经重载过的除法操作符则可以返回一个代表分数的BigRational类型数据。和你所见到的一样,这样的数字均是以标准化的格式存储起来的(这就意味着此分数是一个最简分数)。

 

下面的几行代码用来寻找一个数q,使得(1/2)q介于r2(1/2)(q+1)之间。和复数情况一样,我们也可以使用如pown<这些标准的函数与操作符。这些都已经被重载,因此对任何数字类型都有效。

 

我们可以使用如4N的形式来声明一个BigRational类型的原型。而这并不是系统内置的语言功能。事实上,你可以为你自己的数字类型自定义它的原型表示方法(使用一些受限制的后缀)。这些会在一篇解释如何自定义一个数字类型的文章中讨论到(请看介绍的链接)。

 

F#计算PI

来看几个使用了BigRational类型的大一点的例子,让我们写一个用来估算πF#代码片断。这个例子用了Gregory-Leibniz系列,WikiPedia上给出了此系列的解释。这个系列的前几个数字是4/1 - 4/3 + 4/5 - 4/7等。很容易就可以发现其中的规律——分母每次都加2并且操作符在+ - 上交替变化。这些系列数字计算起来很简单,但是它并不能给出一个精确的评估。但是它已经能够很好的演示实数类型数据的使用方法。

 

我们可以通过把这个序列作为seq<BigRational>类型的值来很好的让代码以函数式的形式表现出来。下面的函数series先计算序列中的一个元素(使用分母n)然后递归的调用它自己来计算剩下的元素:

 1: /// Generates series that approximates PI 
 2: /// 4/1 - 4/3 + 4/5 - 4/7 + 4/9 (starting from q * 4/n)
 3: let rec series n q = 
 4:   seq { 
 5:     yield q * 4N / n
 6:     yield! series (n + 2N) (q * -1N) }
 7: 
 8: let pi1 = series 1N 1N |> Seq.take 10 |> Seq.reduce (+)
 9: val pi1 : BigRational = 44257352/14549535N
10: float pi1
11: val it : float = 3.041839619
12: 
13: let pi2 = series 1N 1N |> Seq.take 300 |> Seq.reduce (+)
14: val pi2 : BigRational = (...)
15: float pi2
16: val it : float = 3.13825933

如果我们将此序列中所有元素都相加,我们将得到精确地π。不幸的是,我们没有无穷的空间与时间来完成这个,因此这个代码片断仅计算了前面的几个元素(使用Seq.take)然后使用Seq.reduce将这些元素相加。第偶数个的元素都是负数,由于series函数将每个元素都乘以q,q则是1-1 中的一个,因此,我们使用+操作符来作为累计函数。

 

C# 中使用F#的数字类型

PowerPack库主要是为F# 而设计的,为之添加了一些功能。与此同时,它也是一个标准的.NET库,因此它也可以被C#引用。且F# 采用同C#一样的方式来表示这些重载过的操作符,因此类型BigRationalComplex能被C#十分方便的使用(当使用.NET2.0时,BigInteger也如此)

 

下面的例子重新实现了之前用来计算πF#代码片断。它采取了与F#一样的处理方式。方法Series用来产生Gregory-Leibniz序列的元素。实现中使用了循环来代替之前的递归,由于在C#中不能方便有效的书写递归迭代:

1: static IEnumerable<BigRational> Series() {
2:   var n = BigRational.FromInt(1);
3:   var q = BigRational.FromInt(1);
4:   while (true) {
5:     yield return q * BigRational.FromInt(4) / n;
6:     n = n + BigRational.FromInt(2);
7:     q = q * BigRational.FromInt(-1);
8:   }
9: }

可以看到,我们可以使用静态方法BigRational.FromInt创建整型的BigRational数据。此类型同样实现了标准的重载操作符(代码使用了+ */)以及一些额外的操作符如BigRational.PowN(用来计算N次方)。为了取得一个浮点型的数来表示π的合适值,我们可以这样写代码:

1: var pi = Series().Take(300).Aggregate((a, b) => a + b);
2: Console.WriteLine(BigRational.ToDouble(pi));

C#中,我们可以使用LINQ来计算序列中的300个值然后相加。所得到的有理数可以通过使用BigRational.ToDouble来将之转为浮点型数据。

 

总结

C#中使用F#PowerPack仅仅是个惊喜。这个库主要是为F# 设计的,但是它遵循了.NET的程序标准模式,因此它也能够被C#完美地使用。在F#PowerPack中实现了的有理数对许多程序来说的有很有用的,但是,如果你需要更多的特性或者更好的效率,那么你可以阅读一下MSDN上的一系列.NET(以及F#)的数字类库。

 

引用与链接

  • Pi  - 估算π的值- WikiPedia

 

posted @ 2012-02-27 14:44  tryfsharp  阅读(1754)  评论(0编辑  收藏  举报