Вот простой перевод формулы в Haskell:
fib n = round $ (phi^n - (1 - phi)^n) / sqrt 5
where phi = (1 + sqrt 5) / 2
Это дает правильные значения только до n = 75
, потому что оно использует Double
арифметику с плавающей точкой точности.* Однако мы можем избежать арифметики с плавающей точкой, работая с числами вида a + b * sqrt 5
!Давайте создадим для них тип данных:
data Ext = Ext !Integer !Integer
deriving (Eq, Show)
instance Num Ext where
fromInteger a = Ext a 0
negate (Ext a b) = Ext (-a) (-b)
(Ext a b) + (Ext c d) = Ext (a+c) (b+d)
(Ext a b) * (Ext c d) = Ext (a*c + 5*b*d) (a*d + b*c) -- easy to work out on paper
-- remaining instance methods are not needed
Мы получаем возведение в степень бесплатно, поскольку оно реализовано в терминах Num
методов.Теперь нам нужно немного изменить формулу, чтобы использовать это.
fib n = divide $ twoPhi^n - (2-twoPhi)^n
where twoPhi = Ext 1 1
divide (Ext 0 b) = b `div` 2^n -- effectively divides by 2^n * sqrt 5
Это дает точный ответ.
Даниэль Фишер указывает, что мы можем использовать формулу phi^n = fib(n-1) + fib(n)*phi
и работать с числами вида a + b * phi
(т.е. ℤ [φ]).Это позволяет избежать неуклюжего шага деления и использовать только одно возведение в степень.Это дает намного лучшую реализацию:
data ZPhi = ZPhi !Integer !Integer
deriving (Eq, Show)
instance Num ZPhi where
fromInteger n = ZPhi n 0
negate (ZPhi a b) = ZPhi (-a) (-b)
(ZPhi a b) + (ZPhi c d) = ZPhi (a+c) (b+d)
(ZPhi a b) * (ZPhi c d) = ZPhi (a*c+b*d) (a*d+b*c+b*d)
fib n = let ZPhi _ x = phi^n in x
where phi = ZPhi 0 1