Haskell-递归堆栈溢出

时间:2019-02-14 07:44:46

标签: haskell recursion stack-overflow

我试图将所有n从1求和到一个很大的数字(现在为10 ** 9),但它会导致堆栈溢出。同样,我不认为在1处停下来并在不同的行中进行求和n是最有效的方法,但是下面的代码是Haskell的全部知识。我对函数式编程真的不太了解,我想尽可能多地解释一下。 (还尝试过将$!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)))

2 个答案:

答案 0 :(得分:10)

chi回答了一个问题,我认为这是主要问题,但是还有其他问题困扰着我。当您说10**9时,您会得到一个浮点数(因为**是“小数”幂)。然后,您将使用浮点相等性来检查递归的基本情况。

summ 1 = ...

问题在于,有可能,而且随着参数变大,由于数值误差,您几乎不会错过基本情况并永远下降为负值。

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)

,依此类推。如果您没有从10 9 调用中获得堆栈溢出,那么您肯定会有无限多个。

您应该在整数上定义函数,这样就不会舍入错误

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 (**)

或使用更宽容的基本情况

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

在任何一种情况下,您都绝对应该按照chi的建议以累加器样式执行此操作,并且还应该绝对放置类型签名。

如果您好奇的话,这里是more on how you get stack overflows in Haskell

答案 1 :(得分:7)

这里的问题是,直到整个10 ^ 9递归调用结束,才可以开始计算总和。本质上,您正在计算

<div class="col-sm-3-1">
 <label for="price-range">range of price</label>
 <input name="price" type="text" class="span2 slider" data-slider min="0" value="" data-slider-max="600" data-slider-step="5" data-slider-value="[0,450]" id="price-range" data-slider-tooltip="show" >
</div>

和括号阻止开始求和。相反,我们希望拥有

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

最简单的方法是使用“累加器”附加参数:

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

为了提高性能,我建议向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 添加类型签名,例如summ


下面的完整程序。此处的运行时间为12秒(summ :: Int -> Double -> Double)。

ghc -O2