2013-06-05 12 views
8

Czy istnieje funkcja dla całkowitej potęgowania w OCaml? ** jest tylko dla pływaków. Chociaż wydaje się, że jest on w większości dokładny, czy nie ma możliwości błędów precyzji, coś w rodzaju 2. ** 3. = 8. czasami zwraca false? Czy istnieje funkcja biblioteki dla całkowitej potęgowania? Mógłbym napisać własne, ale problemy z wydajnością do tego dochodzą, a także byłbym zaskoczony, gdyby nie istniała już taka funkcja.Integer potęgowanie w OCaml

Odpowiedz

10

chodzi część zmiennoprzecinkowa twojego pytania: OCaml wywołuje funkcję podstawowego systemu pow(). Zmiennoprzecinkową potęgowanie jest trudna funkcja do wykonania, ale to dopiero musi być wierny (czyli z dokładnością do jednego Unit in the Last Place), aby 2. ** 3. = 8. ocenić na true, ponieważ 8.0 jest jedynym float ciągu ULP z matematycznie poprawny wynik 8.

Wszystkie biblioteki matematyczne powinny (*) być wierne, więc nie powinieneś martwić się tym konkretnym przykładem. Ale not all of them actually are, więc masz rację martwić.


lepszego powodu martwić się będzie, jeśli używasz 63-bitowe liczby całkowite lub szersze, że argumenty i wynik potęgowania nie można dokładnie reprezentowane OCaml pływaki (właściwie IEEE 754 podwójnej precyzji liczb które nie mogą reprezentować wartości 9_007_199_254_740_993 lub 2 + 1). W tym przypadku, potęgowanie zmiennoprzecinkowe jest złym substytutem dla potęgowania liczby całkowitej, nie z powodu słabości w konkretnej implementacji, ale dlatego, że nie jest zaprojektowane do reprezentowania dokładnie liczb całkowitych, które są duże.


(*) Inna zabawa odniesienie czytać na ten temat jest „A Logarithm Too Clever by Half” William Kahan.

+0

Czy potęgowanie zmiennoprzecinkowe jest tak szybkie, jak potęgowanie liczby całkowitej, biorąc pod uwagę, że argumenty są takie same? Ponadto, dla jasności, twoje stwierdzenie, że potęgowanie zmiennoprzecinkowe powinno być wierne prawdziwe dla wszystkich liczb całkowitych a, b, dla których -2^30 ≤ a^b <2^30, lub właśnie dla mojego szczególnego przykładu 2 3 i 8? – user2258552

+0

@ user2258552 Jeśli chodzi o prędkość: potęgowanie zmiennoprzecinkowe jest prawdopodobnie wolniejsze niż dobrze napisana liczba całkowita. Odnośnie znaczenia "powinien": wierna elementarna funkcja jest dokładna dla jednego ULP w całej jego domenie definicji. Wszystkie libmy powinny być wierne, ponieważ jest to rozsądny kompromis między kosztem obliczeniowym a dokładnością, która sprawiłaby, że wszyscy byliby zadowoleni. Dokładność do 0,5 ULP jest zbyt duża, aby oczekiwać wszystkich libmów z powodu dylematu stołu, ale dokładność do 1 ULP jest możliwa do osiągnięcia przy rozsądnych kosztach. (ale, znowu, pow() jest jedną z najtrudniejszych funkcji elementarnych) –

+0

Jeśli chodzi o prędkość: w świetle tego, to naprawdę nie ma większego sensu, aby nie uwzględniać funkcji potęgowania liczby całkowitej w standardowej bibliotece ... – user2258552

19

Nie w standardowej bibliotece. Ale możesz łatwo napisać samemu (przy użyciu szybkiego dostępu), lub użyć ponownie rozszerzonej biblioteki, która to zapewnia. W Batteries jest to Int.pow.

Poniżej znajduje się proponowana realizacja:

let rec pow a = function 
    | 0 -> 1 
    | 1 -> a 
    | n -> 
    let b = pow a (n/2) in 
    b * b * (if n mod 2 = 0 then 1 else a) 

Jeśli istnieje ryzyko przelania, ponieważ jesteś manipulowania bardzo dużych liczb, powinieneś korzystać z biblioteki dużym całkowitą takie jak Zarith, która zawiera wszystkie rodzaje funkcji potęgowania.

(może być potrzebny „modularne potęgowanie” Komputery (a^n) mod p; można to zrobić w sposób, który pozwala uniknąć przepełnienia stosując mod w obliczeniach pośredniczących, na przykład w funkcji pow powyżej).

+3

Świetna odpowiedź. Niestety, mogłem wybrać tylko jedną najlepszą odpowiedź: /. Ponadto nie jestem przekonany, że jest to zawsze najszybszy sposób implementacji potęgowania liczb całkowitych. W rzeczywistości myślę, że istnieje kwestia projektu Eulera (którą muszę jeszcze rozwiązać) zgodnie z tymi wytycznymi. Naprawdę uważam, że do biblioteki standardowej powinna zostać dodana potęgowanie. Nawet jeśli nie jest to bardziej efektywne (co do którego nie jestem pewien), to dość powszechna rzecz, a konwertowanie i dekonwersja z elementów pływających jest denerwująca. Oczywiście importowanie biblioteki nie jest trudne, ale nie ma powodu, dla którego nie powinno to być standardowe. – user2258552

+2

Cóż, jeśli masz lepsze pomysły na implementację liczb całkowitych w ogólnym przypadku, możesz zasugerować implementację. – gasche

+0

@ user2258552 Nie zgadzam się z twoim założeniem, że całkowita potęgowanie jest tak powszechne. W praktyce prawie zawsze pracujesz z małymi stałymi wykładnikami lub potrzebujesz * dowolnej arytmetyki precyzyjnej, zgodnie z sugestią gasche. TL; DR: Przestań wierzyć, że potrzebujesz wykładniczej liczby całkowitej na ustalonej precyzji i zrozum, że potrzebujesz arytmetycznej biblioteki arytmetycznej. –

3

Oto kolejna realizacja, który wykorzystuje algorytm szybkiego potęgowania (jak to dostarczane przez @gasche), ale ten jest ogon rekurencyjnej

let is_even n = 
    n mod 2 = 0 

(* https://en.wikipedia.org/wiki/Exponentiation_by_squaring *) 
let pow base exponent = 
    if exponent < 0 then invalid_arg "exponent can not be negative" else 
    let rec aux accumulator base = function 
    | 0 -> accumulator 
    | 1 -> base * accumulator 
    | e when is_even e -> aux accumulator (base * base) (e/2) 
    | e -> aux (base * accumulator) (base * base) ((e - 1)/2) in 
    aux 1 base exponent 
+1

Zwróć uwagę, że ogon -rekursywność nie ma znaczenia dla funkcji logarytmicznej w jej danych wejściowych. Jak mogłeś kiedykolwiek zdmuchnąć stos? Oczywiście, jeśli rekursywność ogona daje inny punkt widzenia, który ujawnia coś interesującego na temat kodu lub ułatwia czytanie, to może być nadal interesujący. – gasche

+0

@gasche masz rację. Ten kod nie ma sensu dla 63 lub 31-bitowych liczb całkowitych. Algorytm taki miałby sens w przypadku liczb arbitralnej precyzji. – Halst

+0

"ujawnia coś interesującego": To spowodowało twój komentarz, @gasche. :-) – Mars