Как рассчитывается 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.