当然,在计算大阶乘时避免整数溢出和环绕的最佳方法是不首先计算阶乘。相反,因为
factorial n = product [1..n]
保持[1..n] 作为n 的阶乘表示与计算实际数字一样好——甚至更好。 推迟一个动作,直到绝对不可避免,我们可以在后计算之前对其进行预优化:
bincoef :: Int -> Int -> Int
bincoef n k = factorial n `div`
((factorial k) * factorial (n - k))
= product [1 .. n] `div`
(product [1 .. k] * product [1 .. n-k])
= product [n-k+1 .. n] `div`
product [1 .. k]
= foldl' g 1 $ zip [n, n-1 .. n-k+1] [1 .. k]
where g !acc (a,b) = (acc * a) `div` b
所以现在,
> mapM_ (\n -> print $ map (bincoef n) [5,10..n]) [20,30..60]
[15504,184756,15504,1]
[142506,30045015,155117520,30045015,142506,1]
[658008,847660528,40225345056,137846528820,40225345056,847660528,658008,1]
[2118760,10272278170,2250829575120,47129212243960,126410606437752,47129212243960,
2250829575120,10272278170,2118760,1]
[5461512,75394027566,53194089192720,4191844505805495,51915437974328292,1182645815
64861424,51915437974328292,4191844505805495,53194089192720,75394027566,5461512,1]
> head . filter (not . snd) $ map (\n -> (n, all (> 0) $ map (bincoef n) [1..n])) [1..]
(62,False)
Int 环绕错误首次出现在 n=62。但是它仍然在 n=60 工作,我们可以看到这些数字中有超过 16 个数字,所以没有基于 Double 的计算有希望正常工作,那里。
要进入更高的范围,仍然只使用基于Int 的操作,下一个合乎逻辑的步骤是保持整数的列表与最初提出的一样,或者更好的是作为它们的素数分解,它们是易于乘除;但那时我们已经非常接近于自己重新实现 bignum 算法了,所以不妨使用简单的基于Integer 的代码,
bc :: Integer -> Integer -> Integer
bc n k = product [n-k+1 .. n] `div` product [1 .. k]
哪个“有效”。
> bc 600 199
124988418115780688528958442419612410733294315465732363826979722360319899409241320138
666379143574138790334901309769571503484430553926248548697640619977793300443439200