Как рассчитывается pow () в C?

наш профессор сказал, что вы не можете вычислитьb еслиpow(), потому что pow() использует естественные логарифмы для его вычисления (ab =eb ln a) и поскольку он не определен для отрицательных чисел, его нельзя вычислить. Я попробовал, и он работает до тех пор, пока b является целым числом.

Я искал через math.h и другие файлы, но не удалось найти, как определена функция и что она использует для вычисления. Я также попытался найти интернет, но без всякого успеха. Есть похожие вопросы по Stack Overflow right здесь и здесь (для C#). (последнее хорошо, но я не смог найти исходный код.)

Итак, вопрос в том, как это pow() на самом деле рассчитаны на C? И почему он возвращает ошибку области, когда база конечна и отрицательна, а показатель конечен и не интегрален?

4 ответов


Если вам интересно, как pow функция может быть реализована на практике, вы можете посмотреть исходный код. Существует своего рода "сноровка" для поиска в незнакомых (и больших) кодовых базах, чтобы найти раздел, который вы ищете, и это хорошо, чтобы получить некоторую практику.

одной из реализаций библиотеки C является glibc, которая имеет зеркала на GitHub. Я не нашел официального зеркала, но неофициальное зеркало в https://github.com/lattera/glibc

мы сначала посмотрим на math/w_pow.c файл, который имеет многообещающее имя. Он содержит функцию __pow которых звонки __ieee754_pow, которые мы можем найти в sysdeps/ieee754/dbl-64/e_pow.c (помните, что не все системы являются IEEE-754, поэтому имеет смысл, что математический код IEEE-754 находится в собственном каталоге).

он начинается с нескольких особых случаях:

if (y == 1.0) return x;
if (y == 2.0) return x*x;
if (y == -1.0) return 1.0/x;
if (y == 0) return 1.0;

немного ниже вы найдете ветку с комментарием

/* if x<0 */

это приводит нас к

return (k==1)?__ieee754_pow(-x,y):-__ieee754_pow(-x,y); /* if y even or odd */

так что вы можете видеть, отрицательный x и целое число y, версия glibc pow вычислит pow(-x,y) а затем сделайте результат отрицательным, если y - это странно.

это не единственный способ сделать что-то, но я предполагаю, что это общее для многих реализаций. Вы можете видеть это pow полон особых случаев. Это распространено в библиотечных математических функциях, которые должны правильно работать с недружелюбными входы, как denormals и бесконечность.

на pow функция особенно трудно читать, потому что это сильно оптимизированный код, который делает бит-twiddling на числах с плавающей запятой.

Стандарт C

стандарт C (n1548 §7.12.7.4) имеет это сказать о pow:

ошибка домена возникает, если x конечное и отрицательное, а y конечное, а не целое значение.

так, согласно стандарту C, отрицательный x должны работа.

существует также вопрос о приложении F, которое дает гораздо более жесткие ограничения в отношении как pow работает на системах IEEE-754 / IEC-60559.


второй вопрос (почему он возвращает ошибку домена) уже освещен в комментариях, но добавляет Для полноты: pow принимает два числа и возвращает вещественное число. Применение рационального показателя на отрицательное число на области вещественных чисел в поле комплексных чисел, что результат этой функции (а двойной) не представляют.

Если вам интересно, о фактической реализации, ну, есть много и это зависит от многих факторов, таких как архитектура и уровень оптимизации. Довольно сложно найти тот, который читается легко, но FDLIBM (свободно распространяемый LIBM) имеет один, который, по крайней мере, имеет хорошее объяснение в комментариях:

/* __ieee754_pow(x,y) return x**y
 *
 *            n
 * Method:  Let x =  2   * (1+f)
 *  1. Compute and return log2(x) in two pieces:
 *      log2(x) = w1 + w2,
 *     where w1 has 53-24 = 29 bit trailing zeros.
 *  2. Perform y*log2(x) = n+y' by simulating muti-precision 
 *     arithmetic, where |y'|<=0.5.
 *  3. Return x**y = 2**n*exp(y'*log2)
 *
 * Special cases:
 *  1.  (anything) ** 0  is 1
 *  2.  (anything) ** 1  is itself
 *  3.  (anything) ** NAN is NAN
 *  4.  NAN ** (anything except 0) is NAN
 *  5.  +-(|x| > 1) **  +INF is +INF
 *  6.  +-(|x| > 1) **  -INF is +0
 *  7.  +-(|x| < 1) **  +INF is +0
 *  8.  +-(|x| < 1) **  -INF is +INF
 *  9.  +-1         ** +-INF is NAN
 *  10. +0 ** (+anything except 0, NAN)               is +0
 *  11. -0 ** (+anything except 0, NAN, odd integer)  is +0
 *  12. +0 ** (-anything except 0, NAN)               is +INF
 *  13. -0 ** (-anything except 0, NAN, odd integer)  is +INF
 *  14. -0 ** (odd integer) = -( +0 ** (odd integer) )
 *  15. +INF ** (+anything except 0,NAN) is +INF
 *  16. +INF ** (-anything except 0,NAN) is +0
 *  17. -INF ** (anything)  = -0 ** (-anything)
 *  18. (-anything) ** (integer) is (-1)**(integer)*(+anything**integer)
 *  19. (-anything except 0 and inf) ** (non-integer) is NAN
 *
 * Accuracy:
 *  pow(x,y) returns x**y nearly rounded. In particular
 *          pow(integer,integer)
 *  always returns the correct integer provided it is 
 *  representable.
 *
 * Constants :
 * The hexadecimal values are the intended ones for the following 
 * constants. The decimal values may be used, provided that the 
 * compiler will convert from decimal to binary accurately enough 
 * to produce the hexadecimal values shown.
 */

короче говоря, механизм таков, как вы его описали, и полагается на вычисление логарифма сначала, но со многими частными случаями, которые необходимо учитывать.


предполагая процессор серии x86,pow эквивалентно

double pow(double base, double exp)
{
   return exp2(exp * log2(base));
}

здесь exp2 и log2 являются примитивами ЦП для экспоненциальных и логарифмических операций в базе 2.

различные процессоры по своей сути имеют разные реализации.

теоретически, если бы у вас не было pow вы могли бы написать:

double pow(double base, double exponent)
{
   return exp(exponent * log(base));
}

но это теряет точность над родной версией из-за накопительного округления.

И Дитрих Эпп выяснилось, что я пропустил кучу особых случаев. Тем не менее у меня есть что сказать по поводу округления, которое должно быть разрешено стоять.


pow работает для отрицательных чисел. Он просто не работает, когда база отрицательна, а показатель не является целым числом.

число в форме ax / y фактически включает в себя y-й корень x. Например, при попытке вычислить1/2 вы на самом деле ищет квадратный корень.

Итак, что произойдет, если у вас есть отрицательная база и нецелочисленный показатель? Вы получаете y-й корень отрицательного числа, который урожайность-сложное нереальное число. pow() не работает с комплексными числами, поэтому он, вероятно, вернет NaN.