Коротка відповідь:
Спеціалізація того, pow(x, n)де nє натуральне число, часто корисна для виконання часу . Але загальна загальна бібліотека pow()все ще працює досить (на диво! ) Добре для цієї мети, і абсолютно важливо якомога менше включити до стандартної бібліотеки С, щоб вона була зроблена максимально портативною та максимально простою для впровадження. З іншого боку, це зовсім не перешкоджає знаходженню в стандартній бібліотеці C ++ або STL, що я впевнений, що ніхто не планує використовувати в якійсь вбудованій платформі.
Тепер для довгої відповіді.
pow(x, n)можна зробити набагато швидше у багатьох випадках, спеціалізуючись nна натуральній кількості. Мені довелося використовувати власну реалізацію цієї функції майже для кожної програми, яку я пишу (але я пишу багато математичних програм на С). Спеціалізовану операцію можна зробити 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. Жодне обладнання не має такого екстремального значення обмеження пам’яті, за винятком деяких хитких ПІК з апаратними стеками, які займають лише 3 до 8 викликів функцій.)
Щодо продуктивності, ви здивуєтеся, на що pow(double, double)здатний садовий сорт . Я перевірив сто мільйонів ітерацій на своєму 5-річному IBM Thinkpad з xрівним номером ітерації та nрівним 10. У цьому сценарії pown_lвиграв. glibc pow()займає 12,0 секунди користувача, pownзаймає 7,4 користувачів секунди та pown_lзаймає лише 6,5 секунди користувача. Тож це не надто дивно. Ми цього більш-менш очікували.
Тоді я дозволяю xбути постійним (я встановив його до 2,5), і я перекинувся nвід 0 до 19 сто мільйонів разів. Цього разу, зовсім несподівано, powпереміг glibc , і зсув! Це зайняло всього 2,0 секунди користувача. Моє pownзайняло 9,6 секунди, а pown_lзайняло 12,2 секунди. Що тут сталося? Я зробив ще один тест, щоб з’ясувати.
Я робив те саме, що вище, лише з xрівнем мільйона. Цього разу pownвиграв у 9,6с. pown_lвзяв 12,2s, а глібк-порох - 16,3s. Тепер це зрозуміло! 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 для них уже занадто великий), і він не може бути портативним, якщо для кожної простої математичної функції потрібно включати кожен альтернативний алгоритм, який може бути корисним. Отже, це не відповідає стандарту С.
виноска: під час тестування продуктивності часу я надав своїм функціям відносно щедрі прапори оптимізації ( -s -O2), які, ймовірно, можна порівняти, якщо не гірше, ніж те, що, ймовірно, використовувалося для компіляції glibc у моїй системі (archlinux), тому результати, ймовірно, справедливий Для більш ретельної перевірки, я мав би скласти Glibc себе , і я reeeally не відчуваю , як це робити. Раніше я використовував Gentoo, тому я пам’ятаю, скільки часу займає, навіть коли завдання автоматизоване . Результати для мене достатньо переконливі (а точніше непереконливі). Звичайно, ви можете зробити це самостійно.
Раунд бонусів: спеціалізація pow(x, n)на всі цілі числа є важливою, якщо потрібен точний цілий вихід, що і відбувається. Розглянемо виділення пам'яті для N-мірного масиву з елементами p ^ N. Якщо вимкнути p ^ N навіть по одному, це призведе до можливого випадкового виникнення segfault.