对无限级数的有限前缀求和

Mei*_*ler 7 precision haskell functional-programming numeric numerical-methods

该数字?可以通过以下无穷级数和来计算:

公式

我想定义一个 Haskell 函数roughlyPI,给定一个自然数k,计算从0k值的序列总和。

示例:(roughlyPi 1000或其他)=>3.1415926535897922

我所做的是(在 VS Code 中):

roughlyPI :: Double -> Double 
roughlyPI 0 = 2 
roughlyPI n = e1/e2 + (roughlyPI (n-1)) 
         where 
             e1 = 2**(n+1)*(factorial n)**2 
             e2 = factorial (2*n +1) 
             factorial 0 = 1 
             factorial n = n * factorial (n-1)
Run Code Online (Sandbox Code Playgroud)

但它并没有真正起作用......

*Main> roughlyPI 100 
NaN
Run Code Online (Sandbox Code Playgroud)

我不知道怎么了。顺便说一下,我是 Haskell 的新手。

我真正想要的是能够输入一个PI最后会给我的数字。不可能那么难...

dop*_*ane 9

正如评论中提到的,我们需要避免大的划分,而是在阶乘中散布较小的划分。我们Double用于表示 PI 但也Double有其局限性。例如1 / 0 == Infinity(1 / 0) / (1 / 0) == Infinity / Infinity == NaN

幸运的是,我们可以使用代数来简化公式,并希望延迟我们的Doubles的爆发。通过在我们的阶乘中进行除法,数字不会变得太笨重太快。

f1

f2

f3

此解决方案将计算roughlyPI 1000,但在 1023 上失败,NaN因为2 ^ 1024 :: Double == Infinity。请注意 的每次迭代如何fac进行除法和乘法以帮助防止数字爆炸。如果你试图用计算机来近似 PI,我相信有更好的算法,但我试图让它在概念上尽可能接近你的尝试。

roughlyPI :: Integer -> Double
roughlyPI 0 = 2
roughlyPI k = e + roughlyPI (k - 1)
  where
    k' = fromIntegral k
    e = 2 ** (k' + 1) * fac k / (2 * k' + 1)
      where
        fac 1 = 1 / (k' + 1)
        fac p = (fromIntegral p / (k' + fromIntegral p)) * fac (p - 1)
Run Code Online (Sandbox Code Playgroud)

Double通过进行计算Rationals然后转换为Doublewith realToFrac(归功于@leftaroundabout),我们可以做得比在 1000 之后爆炸更好:

roughlyPI' :: Integer -> Double
roughlyPI' = realToFrac . go
  where
    go 0 = 2
    go k = e + go (k - 1)
      where
        e = 2 ^ (k + 1) * fac k / (2 * fromIntegral k + 1)
          where
            fac 1 = 1 % (k + 1)
            fac p = (p % (k + p)) * fac (p - 1)
Run Code Online (Sandbox Code Playgroud)

如需进一步参考,请参阅有关 PI 近似值的维基百科页面

PS 抱歉,公式庞大,stackoverflow 不支持 LaTex


lef*_*out 5

首先请注意,您的代码实际上有效

*Main> roughlyPI 91
3.1415926535897922
Run Code Online (Sandbox Code Playgroud)

正如已经说过的,问题是当您尝试使近似更好时,阶乘项变得太大而无法用双精度浮点数表示。解决这个问题的最简单的——尽管有点暴力——的方法是用有理算术来代替所有的计算。因为 Haskell 中的数值运算是多态的,所以它可以使用与您几乎相同的代码,只有**运算符不能使用,因为它允许分数指数(通常是无理数)。相反,您应该使用整数指数,这在概念上是正确的。这需要一些fromIntegral

roughlyPI :: Integer -> Rational 
roughlyPI 0 = 2 
roughlyPI n = e1/e2 + (roughlyPI (n-1)) 
         where 
             e1 = 2^(n+1)*fromIntegral (factorial n^2)
             e2 = fromIntegral . factorial $ 2*n + 1
             factorial 0 = 1 
             factorial n = n * factorial (n-1)
Run Code Online (Sandbox Code Playgroud)

这现在也适用于更高程度的近似,尽管需要很长时间来处理所涉及的巨大分数:

*Main> realToFrac $ roughlyPI 1000
3.141592653589793
Run Code Online (Sandbox Code Playgroud)