Krótka odpowiedź:
Specjalizacja pow(x, n)gdzie njest liczbą naturalną jest często przydatna przy wykonywaniu zadań czasowych . Ale typowa biblioteka standardowa pow()nadal działa całkiem (o dziwo! ) Dobrze w tym celu i absolutnie krytyczne jest włączenie jak najmniejszej ilości do standardowej biblioteki C, aby można ją było uczynić tak przenośną i tak łatwą do wdrożenia, jak to tylko możliwe. Z drugiej strony, to wcale nie przeszkadza mu być w standardowej bibliotece C ++ lub STL, którego jestem prawie pewien, że nikt nie planuje używać na jakiejś wbudowanej platformie.
A teraz długa odpowiedź.
pow(x, n)w wielu przypadkach można znacznie przyspieszyć, specjalizując nsię w liczbach naturalnych. Musiałem używać własnej implementacji tej funkcji dla prawie każdego programu, który piszę (ale piszę wiele programów matematycznych w języku C). Specjalistyczną operację można wykonać w O(log(n))czasie, ale gdy njest mała, prostsza wersja liniowa może być szybsza. Oto implementacje obu:
// 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;
}
(Wyszedłem xi wartość zwracana jako podwaja się, ponieważ wynik pow(double x, unsigned n)będzie pasował do podwojenia mniej więcej tak często, jak pow(double, double)będzie to możliwe).
(Tak, pownjest rekurencyjny, ale zerwanie stosu jest absolutnie niemożliwe, ponieważ maksymalny rozmiar stosu będzie z grubsza równy log_2(n)i njest liczbą całkowitą. Jeśli njest to 64-bitowa liczba całkowita, daje to maksymalny rozmiar stosu wynoszący około 64. Żaden sprzęt nie ma tak ekstremalnych możliwości ograniczenia pamięci, z wyjątkiem niektórych podejrzanych PIC ze stosami sprzętowymi, które sięgają tylko 3 do 8 wywołań funkcji głęboko.)
Jeśli chodzi o wydajność, będziesz zaskoczony, do czego pow(double, double)zdolna jest odmiana ogrodowa . Przetestowałem sto milionów iteracji na moim 5-letnim IBM Thinkpad z xrówną liczbą iteracji i nrówną 10. W tym scenariuszu pown_lwygrałem. glibc pow()zajmował 12,0 sekund użytkownika, pown7,4 sekundy użytkownika i pown_ltylko 6,5 sekundy użytkownika. Więc to nie jest zbyt zaskakujące. Spodziewaliśmy się tego mniej więcej.
Następnie pozostawiam xstałą wartość (ustawiam ją na 2,5) i wykonuję pętlę nod 0 do 19 sto milionów razy. Tym razem, dość nieoczekiwanie, powwygrał glibc , i to po miażdżąco! Zajęło to tylko 2,0 sekundy użytkownika. Mój pownzajął 9,6 sekundy i pown_l12,2 sekundy. Co tu się stało? Zrobiłem kolejny test, aby się dowiedzieć.
Zrobiłem to samo, co powyżej, tylko z xrównym milionem. Tym razem pownwygrał przy 9,6 s. pown_lzajęło 12,2 s, a glibc pow 16,3 s. Teraz jest jasne! glibc powdziała lepiej niż te trzy, gdy xjest niski, ale gorzej, gdy xjest wysoki. Kiedy xjest wysoka, pown_ldziała najlepiej, gdy njest niska, i powndziała najlepiej, gdy xjest wysoka.
Oto trzy różne algorytmy, z których każdy może działać lepiej niż inne w odpowiednich okolicznościach. Ostatecznie więc, który z nich najprawdopodobniej zależy od tego, jak planujesz używać pow, ale użycie właściwej wersji jest tego warte, a posiadanie wszystkich wersji jest przyjemne. W rzeczywistości możesz nawet zautomatyzować wybór algorytmu za pomocą takiej funkcji:
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);
}
O ile wartości stałe x_expectedi n_expectedsą ustalane w czasie kompilacji, wraz z prawdopodobnie kilkoma innymi zastrzeżeniami, warty swojej soli kompilator optymalizujący automatycznie usunie całe pown_autowywołanie funkcji i zastąpi je odpowiednim wyborem spośród trzech algorytmów. (Teraz, jeśli rzeczywiście zamierzasz tego użyć , prawdopodobnie będziesz musiał się trochę bawić, ponieważ nie próbowałem dokładnie skompilować tego, co napisałem powyżej.;))
Z drugiej strony, glibc pow działa, a glibc jest już wystarczająco duży. Standard C ma być przenośny, w tym na różne urządzenia wbudowane (w rzeczywistości deweloperzy oprogramowania wbudowanego na całym świecie ogólnie zgadzają się, że glibc jest już dla nich za duży) i nie może być przenośny, jeśli dla każdej prostej funkcji matematycznej musi zawierać wszystkie alternatywny algorytm, który może być przydatny. Dlatego właśnie nie jest w standardzie C.
przypis: Podczas testowania wydajności w czasie dałem moim funkcjom stosunkowo duże flagi optymalizacji ( -s -O2), które prawdopodobnie będą porównywalne, jeśli nie gorsze, z tym, co prawdopodobnie zostało użyte do kompilacji glibc w moim systemie (archlinux), więc wyniki są prawdopodobnie targi. Aby uzyskać bardziej rygorystyczny test, musiałbym sam skompilować glibc i naprawdę nie mam na to ochoty. Kiedyś używałem Gentoo, więc pamiętam, ile czasu to zajmuje, nawet jeśli zadanie jest zautomatyzowane . Wyniki są dla mnie wystarczająco rozstrzygające (lub raczej niejednoznaczne). Oczywiście możesz to zrobić samodzielnie.
Runda bonusowa: specjalizacja pow(x, n)wszystkich liczb całkowitych ma znaczenie instrumentalne, jeśli wymagana jest dokładna liczba całkowita, co się zdarza. Rozważ przydzielenie pamięci dla tablicy N-wymiarowej z elementami p ^ N. Usunięcie p ^ N nawet o jeden spowoduje prawdopodobnie przypadkowe wystąpienie błędu.