2013-06-05 11 views
8

Gibt es eine Funktion für Integer-Potenzierung in OCaml? ** ist nur für Schwimmer. Obwohl es meistens genau zu sein scheint, gibt es keine Möglichkeit für Präzisionsfehler, etwa 2. ** 3. = 8. manchmal falsch? Gibt es eine Bibliotheksfunktion für die Integer-Potenzierung? Ich könnte mein eigenes schreiben, aber da drängen sich Effizienzprobleme auf, und auch ich würde mich wundern, wenn es nicht schon eine solche Funktion gibt.Integer-Exponentiation in OCaml

Antwort

10

Bezüglich Der Fließkommateil Ihrer Frage: OCaml ruft die pow() Funktion des zugrunde liegenden Systems auf. Floating-Point-Exponentiation ist eine schwierige Funktion zu implementieren, aber es muss nur treu sein (das heißt, genau zu Unit in the Last Place), 2. ** 3. = 8. zu true bewerten, weil 8.0 der einzige float innerhalb einer ULP der mathematisch korrekte Ergebnis 8 ist.

Alle mathematischen Bibliotheken sollten (*) treu sein, also sollten Sie sich über dieses spezielle Beispiel keine Gedanken machen. Aber not all of them actually are, so dass Sie sich Sorgen machen müssen.


Ein besserer Grund wäre, sich Sorgen zu machen, wenn Sie mit 63-Bit-Integer oder breiter, dass die Argumente oder das Ergebnis der Potenzierung nicht exakt dargestellt werden, wie OCaml schwimmt (eigentlich IEEE 754 mit doppelter Genauigkeit Zahlen das kann 9_007_199_254_740_993 oder 2 + 1) nicht darstellen. In diesem Fall ist die Fließkomma-Potenzierung ein schlechter Ersatz für die Potenzierung von Ganzzahlen, nicht wegen einer Schwäche in einer bestimmten Implementierung, sondern weil sie nicht dafür ausgelegt ist, genau so große Ganzzahlen darzustellen.


(*) Ein weiterer Spaß Bezug zu diesem Thema zu lesen ist „A Logarithm Too Clever by Half“ von William Kahan.

+0

Ist die Fließkomma-Potenzierung so schnell wie die Ganzzahl-Potenzierung, wenn die Argumente gleich sind? Auch um klar zu sein, ist Ihre Aussage, dass die Gleitkomma-Exponentiation für alle ganzen Zahlen a, b, für die -2^30 ≤ a^b <2^30 gilt, oder nur für mein spezielles Beispiel von 2 3 und 8? – user2258552

+0

@ user2258552 In Bezug auf die Geschwindigkeit: Floating-Point-Exponentiation ist wahrscheinlich langsamer als eine gut geschriebene ganze Zahl. In Bezug auf die Bedeutung von "sollte": Eine zuverlässige elementare Funktion ist genau für eine ULP über ihre Definitionsdomäne hinweg gültig. Alle libms sollten treu sein, weil es ein vernünftiger Kompromiss zwischen Rechenaufwand und Genauigkeit ist, der so ziemlich jeden glücklich machen würde. Die Genauigkeit von 0,5 ULP ist aufgrund des Dilemma des Tischmachers ein bisschen zu viel von allen libms zu erwarten, aber die Genauigkeit von 1 ULP ist zu vernünftigen Kosten erreichbar. (aber wiederum ist pow() eine der härtesten elementaren Funktionen) –

+0

In Bezug auf die Geschwindigkeit: Angesichts dieser Tatsache macht es wirklich wenig Sinn, keine Ganzzahl-Exponentierungsfunktion in der Standardbibliothek zu verwenden ... – user2258552

19

Nicht in der Standardbibliothek. Aber Sie können auch einfach selbst einen schreiben (mit exponentiation by squaring, um schnell zu sein) oder eine erweiterte Bibliothek, die dies bietet, wiederverwenden. In Batteries ist es Int.pow.

Nachfolgend finden Sie eine vorgeschlagene Umsetzung:

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) 

Wenn die Gefahr von Überlauf ist, weil Sie sehr große Zahlen sind manipuliert, werden Sie wahrscheinlich eine große ganzzahlige Bibliothek wie Zarith, die alle Arten bietet verwenden sollten von Potenzierungsfunktionen.

(Sie können die „modulare Potenzierung“ benötigen, Rechen (a^n) mod p, dies kann in einer Weise geschehen, die pow oben überläuft, indem die Mod in den dazwischen liegenden Berechnungen, zum Beispiel in der Funktion vermeidet.)

+3

Große Antwort. Leider konnte ich nur eine beste Antwort wählen: /. Ich bin auch nicht davon überzeugt, dass dies immer der schnellste Weg ist, die Potenzierung der ganzen Zahl zu implementieren. Tatsächlich denke ich, dass es eine Projekt-Euler-Frage (die ich noch lösen muss) in dieser Richtung gibt. Ich glaube wirklich, die Integer-Potenzierung sollte der Standard-Bibliothek hinzugefügt werden. Auch wenn es nicht effizienter ist (was ich nicht sicher bin), ist es eine ziemlich häufige Sache zu brauchen und zu konvertieren und von Floats zu dekonvertieren ist ärgerlich. Natürlich ist der Import einer Bibliothek nicht schwer, aber es gibt keinen Grund, dass dies nicht Standard sein sollte. – user2258552

+2

Nun, wenn Sie bessere Ideen haben, wie Sie die Ganzzahlimplementierung im allgemeinen Fall implementieren können, zögern Sie nicht, eine Implementierung vorzuschlagen. – gasche

+0

@ user2258552 Ich stimme Ihrer Prämisse nicht zu, dass Integer-Exponentiation so häufig ist. In der Praxis arbeiten Sie fast immer mit kleinen festen Exponenten, oder Sie benötigen * eine willkürliche Präzisionsarithmetik, wie von gasche vorgeschlagen. TL; DR: Stoppen Sie zu glauben, dass Sie die Ganzzahl-Exponentiation auf Festpräzisions-Ints benötigen und dass Sie eine arithmetische Bibliothek mit beliebiger Genauigkeit benötigen. –

3

Hier ist eine andere Implementierung, die Potenzierung verwendet durch Quadrierung (wie die von @gasche zur Verfügung gestellt), aber dieses ist Schwanz-rekursive

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

Beachten Sie diesen Tail Für eine logarithmische Funktion in ihrer Eingabe spielt die Rekursivität keine Rolle. Wie konntest du jemals den Stapel blasen? Wenn Tail-Recursivity einen anderen Blickwinkel bietet, der etwas Interessantes über den Code enthüllt oder das Lesen erleichtert, kann es natürlich immer noch interessant sein. – gasche

+0

@gasche du hast Recht. Dieser Code ist für 63 oder 31-Bit-Ganzzahlen nicht sinnvoll. Ein solcher Algorithmus wäre für Zahlen mit beliebiger Genauigkeit sinnvoll. – Halst

+0

"enthüllt etwas Interessantes": Es hat Ihren Kommentar ausgelöst, @gasche. :-) – Mars