我试图将所有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)))
答案 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