让我用一个例子来解释成本敏感折叠的含义:用任意精度计算pi。我们可以使用Leibniz formula(不是非常有效,但很好,很简单)和懒惰的列表,如下所示:
pi = foldr1 (+) [(fromIntegral $ 4*(-1)^i)/(fromIntegral $ 2*i+1) | i<-[0..]]
现在,显然这个计算永远不会完成,因为我们必须计算无限列表中的每个值。但实际上,我不需要pi的确切值,我只需要它到指定的小数位数。我可以像这样定义pi':
pi' n = foldr1 (+) [(fromIntegral $ 4*(-1)^i)/(fromIntegral $ 2*i+1) | i<-[0..n]]
但是根本不清楚我需要传递什么值以获得我想要的精度。我需要的是某种对成本敏感的折叠,只要达到所需的精度就会停止折叠。这样的折叠是否存在?
(请注意,在这种情况下很容易看出我们是否达到了所需的精度。因为Leibniz公式使用了一个与每个项交替符号的序列,所以误差总是小于下一个的绝对值术语在序列中。)
编辑:拥有成本敏感的折叠非常酷,这也可以考虑计算时间/功耗。例如,我想要最精确的pi值,因为我有1小时的计算时间和10kW小时的花费。但我意识到这将不再具有严格的功能。
答案 0 :(得分:10)
我的建议是使用扫描而不是折叠。然后遍历结果列表,直到找到所需的精度。左扫描(scanl
)的一个有用特例是iterate
函数:
piList :: [Double]
piList =
map (4*) .
scanl (+) 0 .
map recip .
iterate (\x -> -(x + 2 * signum x)) $ 1
您现在可以遍历此列表。例如,您可能会检查某个精度的更改何时变为不可见:
findPrec :: (Num a, Ord a) => a -> [a] -> Maybe a
findPrec p (x0:x1:xs)
| abs (x1 - x0) <= p = Just x0
| otherwise = findPrec p (x1:xs)
findPrec _ _ = Nothing
答案 1 :(得分:5)
Haskell这样做的方法是生成一个更加精确答案的无限列表,然后以正确的准确度到达并抓住一个答案。
import Data.List (findIndex)
pis = scanl (+) 0 [4*(-1)**i/(2*i+1) | i <- [0..]]
accuracies = zipWith (\x y -> abs (x-y)) pis (tail pis)
piToWithin epsilon = case findIndex (<epsilon) accuracies of
Just n -> pis !! n
Nothing -> error "Wow, a non-terminating loop terminated!"
答案 2 :(得分:2)
一般情况下,你问的折叠不存在。您必须自己提供准确性估计。它通常可能是问题,但是所有实际有用的序列对于部分和的数值精确度确实具有合理的上限估计,通常由其他人获得。但是,我应该鼓励你阅读相关的教科书,比如数值分析教科书,这些教科书通常都有关于估算无限数字序列和的一部分,并给出了高估。
然而,有一般规则,如果数值过程有限制,那么数值变化就会变为零,因为粗略的几何级数,所以如果两个后续的变化是1.5和1.0,那么下面的变化将是大约0.6和所以(最好在几个最后成员名单上积累这样的估计,而不仅仅是2个成员)。使用此规则和方程来计算几何级数的总和,通常可以找到合理的数值精度估算值。注意:这是经验规则(它有名称,但我忘记了),而不是严格的定理。此外,IEEE Double / Float的表示具有有限的精度,并且在某些时候从序列尾部添加小数字将不会改变计算的部分和。我们鼓励您阅读有关此案例的x86中的浮点表示,您可能会找到您的折叠。
总结:一般没有解决方案,但通常在实践中对大多数有用的序列有合理的估计,通常从文献中获得每种序列类型或硬件的数值限制
答案 3 :(得分:1)
上面提到的Daniel Wagner建议的一些很好的例子可以在论文中找到Why Functional Programming Matters
本文的具体例子是:迭代寻根,数值微分和数值积分。