czytałem kod realizacji (^) z biblioteki standardowej Haskell:Realizacja (^)
(^) :: (Num a, Integral b) => a -> b -> a
x0^y0 | y0 < 0 = errorWithoutStackTrace "Negative exponent"
| y0 == 0 = 1
| otherwise = f x0 y0
where -- f : x0^y0 = x^y
f x y | even y = f (x * x) (y `quot` 2)
| y == 1 = x
| otherwise = g (x * x) ((y - 1) `quot` 2) x
-- g : x0^y0 = (x^y) * z
g x y z | even y = g (x * x) (y `quot` 2) z
| y == 1 = x * z
| otherwise = g (x * x) ((y - 1) `quot` 2) (x * z)
Teraz ta część, gdzie g jest zdefiniowana wydaje się dziwne dla mnie, dlaczego nie wystarczy wdrożyć go jak to:
expo :: (Num a ,Integral b) => a -> b ->a
expo x0 y0
| y0 == 0 = 1
| y0 < 0 = errorWithoutStackTrace "Negative exponent"
| otherwise = f x0 y0
where
f x y | even y = f (x*x) (y `quot` 2)
| y==1 = x
| otherwise = x * f x (y-1)
Ale rzeczywiście podłączenie powiedzmy 3^1000000 pokazuje, że (^) jest około 0,04 sekundy szybciej niż expo.
Dlaczego numer
(^)
jest szybszy niżexpo
?
Z pewnością nie jest to bardzo dramatyczny problem, ale przypuszczam, że to, co jest nieefektywne w tej wersji, to to, że wykonuje wiele multiplikacji małej liczby 'x' z już dużym wynikiem' f'. Wersja standardowa generuje bardziej zrównoważone drzewo mnożenia, wysyłając pozostałe czynniki przez cały czas trwania wywołania rekursji. – leftaroundabout
'g' jest rekurencyjny, podczas gdy ostatnie' f' nie jest. Myślę, że to ma znaczenie tutaj. – chi