Kurze Antwort:
Eine Spezialisierung pow(x, n), wo neine natürliche Zahl ist oft nützlich für Pünktlichkeit . Aber das Generikum der Standardbibliothek pow()funktioniert für diesen Zweck immer noch ziemlich ( überraschend! ) Gut und es ist absolut wichtig, so wenig wie möglich in die Standard-C-Bibliothek aufzunehmen, damit sie so portabel und so einfach wie möglich implementiert werden kann. Auf der anderen Seite hindert das sie überhaupt nicht daran, in der C ++ - Standardbibliothek oder der STL zu sein, und ich bin mir ziemlich sicher, dass niemand vorhat, sie in einer Art eingebetteter Plattform zu verwenden.
Nun zur langen Antwort.
pow(x, n)kann in vielen Fällen viel schneller gemacht werden, indem man sich nauf eine natürliche Zahl spezialisiert. Ich musste für fast jedes Programm, das ich schreibe, meine eigene Implementierung dieser Funktion verwenden (aber ich schreibe viele mathematische Programme in C). Die spezialisierte Operation kann rechtzeitig durchgeführt O(log(n))werden, aber wenn sie nklein ist, kann eine einfachere lineare Version schneller sein. Hier sind Implementierungen von beiden:
// Computes x^n, where n is a natural number.
double pown(double x, unsigned n)
{
double y = 1;
// n = 2*d + r. x^n = (x^2)^d * x^r.
unsigned d = n >> 1;
unsigned r = n & 1;
double x_2_d = d == 0? 1 : pown(x*x, d);
double x_r = r == 0? 1 : x;
return x_2_d*x_r;
}
// The linear implementation.
double pown_l(double x, unsigned n)
{
double y = 1;
for (unsigned i = 0; i < n; i++)
y *= x;
return y;
}
(Ich bin gegangen xund der Rückgabewert verdoppelt sich, weil das Ergebnis von pow(double x, unsigned n)ungefähr so oft wie möglich in ein Doppel passt pow(double, double).)
(Ja, pownist rekursiv, aber das Brechen des Stapels ist absolut unmöglich, da die maximale Stapelgröße ungefähr gleich ist log_2(n)und neine Ganzzahl ist. Wenn nes sich um eine 64-Bit-Ganzzahl handelt, erhalten Sie eine maximale Stapelgröße von etwa 64. Keine Hardware ist so extrem Speicherbeschränkungen, mit Ausnahme einiger zwielichtiger PICs mit Hardware-Stacks, die nur 3 bis 8 Funktionsaufrufe tief gehen.)
Was die Leistung angeht, werden Sie überrascht sein, wozu eine Gartensorte pow(double, double)in der Lage ist. Ich habe hundert Millionen Iterationen auf meinem 5 Jahre alten IBM Thinkpad getestet x, die der Iterationsnummer und n10 entsprechen. In diesem Szenario habe ich pown_lgewonnen. glibc pow()dauerte 12,0 Benutzersekunden, pown7,4 Benutzersekunden und pown_lnur 6,5 Benutzersekunden. Das ist also nicht allzu überraschend. Wir haben das mehr oder weniger erwartet.
Dann lasse xich konstant sein (ich setze es auf 2,5) und habe nhundert Millionen Mal von 0 auf 19 geschleift . Diesmal powgewann glibc ganz unerwartet und durch einen Erdrutsch! Es dauerte nur 2,0 Sekunden. Ich pownbrauchte 9,6 Sekunden und pown_l12,2 Sekunden. Was ist hier passiert? Ich habe einen weiteren Test gemacht, um das herauszufinden.
Ich habe das Gleiche wie oben gemacht, nur mit xeiner Million. Diesmal powngewann bei 9,6s. pown_ldauerte 12,2 s und glibc pow dauerte 16,3 s. Jetzt ist es klar! glibc powschneidet besser ab als die drei, wenn xes niedrig ist, aber am schlechtesten, wenn xes hoch ist. Wenn xhoch ist, ist pown_ldie beste Leistung, wenn nes niedrig ist, und powndie beste Leistung, wenn xes hoch ist.
Hier sind drei verschiedene Algorithmen, von denen jeder unter den richtigen Umständen eine bessere Leistung als die anderen erzielen kann. Also, letztlich, die am ehesten zu verwenden , hängt davon ab , wie Sie planen , über die Verwendung pow, aber die richtige Version verwenden ist es wert, und ist schön , alle Versionen haben. Mit einer Funktion wie der folgenden können Sie sogar die Auswahl des Algorithmus automatisieren:
double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
if (x_expected < x_threshold)
return pow(x, n);
if (n_expected < n_threshold)
return pown_l(x, n);
return pown(x, n);
}
Solange x_expectedund n_expectedzum Zeitpunkt der Kompilierung Konstanten festgelegt werden, entfernt ein Optimierungs-Compiler, der es wert ist, automatisch den gesamten pown_autoFunktionsaufruf und ersetzt ihn durch die entsprechende Auswahl der drei Algorithmen. (Wenn Sie nun tatsächlich versuchen wollen, dies zu verwenden , müssen Sie wahrscheinlich ein wenig damit spielen, da ich nicht genau versucht habe , das zu kompilieren, was ich oben geschrieben habe .;))
Auf der anderen Seite pow funktioniert glibc und glibc ist bereits groß genug. Der C-Standard soll portabel sein, auch für verschiedene eingebettete Geräte (tatsächlich sind sich eingebettete Entwickler überall im Allgemeinen einig, dass glibc bereits zu groß für sie ist), und er kann nicht portabel sein, wenn für jede einfache mathematische Funktion jede enthalten sein muss alternativer Algorithmus, könnte von nutzen sein. Deshalb ist es nicht im C-Standard.
Fußnote: Beim Testen der -s -O2Zeitleistung habe ich meinen Funktionen relativ großzügige Optimierungsflags ( ) gegeben, die wahrscheinlich mit dem vergleichbar sind, wenn nicht sogar schlechter als das, was wahrscheinlich zum Kompilieren von glibc auf meinem System (archlinux) verwendet wurde. Die Ergebnisse sind also wahrscheinlich Messe. Für einen strengeren Test müsste ich glibc selbst kompilieren und ich habe wirklich keine Lust dazu. Früher habe ich Gentoo verwendet, daher erinnere ich mich, wie lange es dauert, auch wenn die Aufgabe automatisiert ist . Die Ergebnisse sind für mich schlüssig (oder eher nicht schlüssig) genug. Sie können dies natürlich gerne selbst tun.
Bonusrunde: Eine Spezialisierung pow(x, n)auf alle Ganzzahlen ist von entscheidender Bedeutung, wenn eine genaue Ganzzahlausgabe erforderlich ist. Ziehen Sie in Betracht, Speicher für ein N-dimensionales Array mit p ^ N Elementen zuzuweisen. Wenn p ^ N auch nur um eins ausgeschaltet wird, tritt möglicherweise ein zufällig auftretender Segfault auf.