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#一样的方式来表示这些重载过的操作符,因此类型BigRational和Complex能被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#)的数字类库。
引用与链接
- F# PowerPack(原代码与二进制包)- CodePlex
- 数字计算 – MSDN上的现实中的函数式编程
- Pi - 估算π的值- WikiPedia
- F#与.NET的数字语言库 - MSDN上的现实中的函数式编程