Короткий ответ:
Специализация pow(x, n)в nвиде натурального числа часто бывает полезна для измерения времени . Но универсальный стандарт стандартной библиотеки по- pow()прежнему хорошо работает (что удивительно! ) Для этой цели, и абсолютно важно включать как можно меньше в стандартную библиотеку C, чтобы ее можно было сделать максимально переносимой и максимально простой в реализации. С другой стороны, это вовсе не мешает ему находиться в стандартной библиотеке C ++ или STL, которые, я почти уверен, никто не планирует использовать в какой-то встроенной платформе.
А теперь длинный ответ.
pow(x, n)во многих случаях можно сделать намного быстрее, если nиспользовать натуральное число. Мне приходилось использовать мою собственную реализацию этой функции почти для каждой программы, которую я пишу (но я пишу много математических программ на C). Специализированная операция может быть выполнена O(log(n))вовремя, но когда nона небольшая, более простая линейная версия может быть быстрее. Вот реализации обоих:
// 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;
}
(Я оставил, xи возвращаемое значение удваивается, потому что результат pow(double x, unsigned n)будет вписываться в удвоение примерно так часто, как pow(double, double)будет.)
(Да, pownэто рекурсивно, но сломать стек абсолютно невозможно, так как максимальный размер стека будет примерно равным log_2(n)и nявляется целым числом. Если nэто 64-битное целое число, это дает максимальный размер стека около 64. Никакое оборудование не имеет таких экстремальных значений. ограничения памяти, за исключением некоторых хитрых PIC с аппаратными стеками, которые обрабатывают от 3 до 8 вызовов функций.)
Что касается производительности, вы будете удивлены тем, на что pow(double, double)способны садовые сорта . Я протестировал сто миллионов итераций на моем 5-летнем IBM Thinkpad с xномером итерации, nравным 10. В этом сценарии pown_lпобедил. glibc pow()занял 12,0 пользовательских секунды, pown7,4 пользовательских секунды и pown_lвсего 6,5 пользовательских секунды. Так что это не слишком удивительно. Этого мы более или менее ожидали.
Затем я позволил xбыть постоянным (я установил его на 2,5) и nсделал цикл от 0 до 19 сто миллионов раз. На этот раз совершенно неожиданно powпобедила glibc , причем безоговорочно! Потребовалось всего 2,0 пользовательских секунды. My pownзанял 9,6 секунды и pown_l12,2 секунды. Что здесь случилось? Я сделал еще один тест, чтобы узнать.
Я сделал то же самое, только с xравным миллиону. На этот раз pownвыиграл с результатом 9,6 с. pown_lзанял 12,2 секунды, а glibc pow - 16,3 секунды. Теперь ясно! glibc powработает лучше трех при xнизком уровне и хуже всего при xвысоком. Когда xвысокий, pown_lлучше всего работает, когда nнизкий, и pownлучше всего работает, когда xвысокий.
Итак, вот три разных алгоритма, каждый из которых может работать лучше других при правильных обстоятельствах. Таким образом, в конечном счете, что использовать , скорее всего , зависит от того, как вы планируете использовать pow, но используя правильную версию это стоит, и иметь все версии хорошо. Фактически, вы даже можете автоматизировать выбор алгоритма с помощью такой функции:
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);
}
Пока x_expectedи n_expectedявляются константами, определяемыми во время компиляции, наряду, возможно, с некоторыми другими предостережениями, оптимизирующий компилятор, стоящий его соли, автоматически удалит весь pown_autoвызов функции и заменит его соответствующим выбором из трех алгоритмов. (Теперь, если вы действительно собираетесь попробовать это использовать , вам, вероятно, придется немного поиграть с этим, потому что я точно не пытался скомпилировать то, что написал выше.;))
С другой стороны, glibc pow действительно работает, и glibc уже достаточно большой. Стандарт C должен быть переносимым, в том числе на различные встроенные устройства (на самом деле встроенные разработчики во всем мире в целом согласны с тем, что glibc уже слишком велик для них), и он не может быть переносимым, если для каждой простой математической функции необходимо включать все альтернативный алгоритм, который может пригодиться. Вот почему этого нет в стандарте C.
сноска: во время тестирования производительности времени я дал своим функциям относительно щедрые флаги оптимизации ( -s -O2), которые, вероятно, будут сопоставимы, если не хуже, чем то, что, вероятно, использовалось для компиляции glibc в моей системе (archlinux), поэтому результаты, вероятно, будут Справедливая. Для более тщательной проверки, я должен был бы составить Glibc себя , и я reeeally не чувствую , как это делать. Раньше я использовал Gentoo, поэтому помню, сколько времени это занимает, даже если задача автоматизирована . Результаты для меня убедительны (или, скорее, неубедительны). Вы, конечно, можете сделать это сами.
Бонусный раунд: специализация pow(x, n)для всех целых чисел важна, если требуется точный целочисленный вывод, что действительно происходит. Рассмотрите возможность выделения памяти для N-мерного массива с элементами p ^ N. Отключение p ^ N даже на единицу приведет к возможной случайной ошибке segfault.