简体   繁体   English

Haskell - 递归堆栈溢出

[英]Haskell - Recursion Stack Overflow

I am trying to sum all n from 1 to a very large number (10**9 for now) but it gives stack overflow. 我试图将所有n从1加到一个非常大的数字(现在是10 ** 9),但它会给出堆栈溢出。 Also I don't think putting a stop at 1 and doing the sum n in different rows is the most efficient way but the code below is all my knowledge for Haskell. 另外我不认为在1处停止并且在不同行中执行总和n是最有效的方式,但下面的代码是我对Haskell的全部知识。 I really don't know much about functional programming and I would like as much explanation as possible. 我真的不太了解函数式编程,我想尽可能多的解释。 (Also I have tried putting $! strict in the last line which was told in other places but it changed nothing. I would be glad if you explained the most efficient way I could do this recursive function.) (我也试过把$!strict放在最后一行,这在其他地方被告知但是没有改变。如果你解释了我能做这种递归函数最有效的方法,我会很高兴的。)

main :: IO()

summ 1 = 1
summ n = 1/(n**2) + summ (n-1)

expected_value = pi*pi/6
errorPercent n = n / expected_value * 100

main = do
    putStr "%"
    print (errorPercent (summ $! (10**9)))

chi has answered one bit of the question, which I think is the main problem, but there is something else that is bugging me. chi回答了一个问题,我认为这是主要的问题,但还有其他一些问题让我烦恼。 When you say 10**9 , you get a floating point number (because ** is "fractional" exponentiation). 当你说10**9 ,你得到一个浮点数 (因为**是“分数”取幂)。 And then you are using floating point equality to check for the base case of your recursion. 然后,您使用浮点相等来检查递归的基本情况。

summ 1 = ...

The problem with this is that it is possible, and as the argument gets larger, likely, that because of numerical error you will just barely miss the base case and descend into negative values forever. 这样做的问题在于它是可能的,并且随着参数变大,可能会因为数值误差而几乎不会错过基本情况并永远下降到负值。

summ 4 =        ... summ 3
summ 3 =        ... summ 2.000001
summ 2.000001 = ... summ 1.000001 
summ 1.000001 = ... summ 0.000001  -- BASE CASE MISSED!
summ 0.000001 = ... summ (-1.000001)
summ (-1.000001) = ... summ (-2.000001)

and so on. 等等。 If you didn't get a stack overflow from 10 9 calls, you surely will with infinitely many. 如果你没有从10 9个电话中获得堆栈溢出,你肯定会无限多。

You should either define your function on integers so there is no rounding error 您应该在整数上定义函数,这样就没有舍入错误

summ :: Int -> Double
summ 1 = 1
summ n = 1 / (fromIntegral n ** 2) + summ (n - 1)
--            ^^^^^^^^^^^^
-- conversion necessary to go from Int to Double

main = ... print (summ (10 ^ 9))
--                      ^^^^^^
--      use integral exponentiation (^) instead of (**)

or use a more forgiving base case 或使用更宽容的基础案例

summ :: Double -> Double
summ n | n <= 1 = 1
summ n = 1 / (n ** 2) + summ (n - 1)

In either case, you should definitely take chi's suggestion to do this in accumulator style, and you should also definitely put a type signature. 在任何一种情况下,你绝对应该采取chi的建议,以累加器的方式做到这一点,你也应该明确地设置类型签名。

Here's more on how you get stack overflows in Haskell if you are curious. 如果你好奇的话, 更多关于你如何在Haskell中获得堆栈溢出的问题

The problem here is that sums can not start being computed until the whole 10^9 recursion calls are over. 这里的问题是,在整个10 ^ 9递归调用结束之前,总和无法开始计算。 Essentially, you are computing 从本质上讲,你是在计算

1/(n**2) + ( 1/((n-1)**2) + ( 1/((n-2)**2) + ....

and the parentheses prevent to start summing. 并且括号阻止开始求和。 Instead, we would like to have 相反,我们希望拥有

(( 1/(n**2) + 1/((n-1)**2) ) + 1/((n-2)**2) ) + ....

The easiest way is to use an "accumulator" additional argument: 最简单的方法是使用“累加器”附加参数:

summ 1 acc = 1 + acc
summ n acc = summ (n-1) $! acc + 1/(n**2)

main = do
    putStr "%"
    print (errorPercent (summ (10^9) 0))  -- set acc to 0 at the beginning

For improving the performance, I'd recommend to add a type signature to summ eg summ :: Int -> Double -> Double . 为了提高性能,我建议在summ添加一个类型签名,例如summ :: Int -> Double -> Double


Full program below. 完整的程序如下。 This runs in 12s here ( ghc -O2 ). 这里运行12秒( ghc -O2 )。

summ :: Int -> Double -> Double
summ 1 acc = 1 + acc
summ n acc = summ (n-1) $! acc + 1 / (fromIntegral n**2)

main :: IO ()
main = do
    putStr "%"
    print (summ (10^9) 0)  -- set acc to 0 at the beginning

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM