How is pow() calculated in C?

前端 未结 4 2114
慢半拍i
慢半拍i 2021-01-05 09:42

Our professor said that you can\'t calculate ab if a<0 using pow() because pow() uses natural logarithms to calculate it (ab

相关标签:
4条回答
  • 2021-01-05 10:00

    Assuming an x86 series processor, pow is the equivalent of

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

    Where exp2 and log2 are CPU primitives for the exponential and logarithm operations in base 2.

    Different CPUs inherently have different implementations.

    In theory if you didn't have pow you could write:

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

    but this loses precision over the native version due to accumulative roundoff.

    And Dietrich Epp revealed I missed a bunch of special cases. Nevertheless I have something to say about roundoff that should be allowed to stand.

    0 讨论(0)
  • 2021-01-05 10:07

    The second question (why does it return a domain error) is already covered in the comments, but adding for completeness: pow takes two real numbers and returns a real number. Applying a rational exponent on a negative number takes you out of the domain of real numbers into the domain of complex numbers, which the result of this function (a double) can't represent.

    If you're curious about the actual implementation, well, there are many and it depends on many factors, such as architecture and level of optimisation. It's quite difficult to find one that reads easily, but FDLIBM (Freely Distributable LIBM) has one which has at least has a good explanation in the comments:

    /* __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.
     */
    

    So, in short, the mechanism is as you described it and relies on calculating the logarithm first, but with many special cases that need to be accounted for.

    0 讨论(0)
  • 2021-01-05 10:21

    pow does work for negative numbers. It just doesn't work when the base is negative and the exponent is not an integer.

    A number in the form ax/y actually involves the y-th root of x. For instance, when you try to calculate a1/2 you are actually looking for the square root of a.

    So what happens if you have a negative base and a non-integer exponent? You get a y-th root of a negative number, which yields is a complex non-real number. pow() does not work with complex numbers, so it will probably return NaN.

    0 讨论(0)
  • 2021-01-05 10:22

    If you're curious how the pow function might be implemented in practice, you can look at the source code. There is a kind of "knack" to searching through unfamiliar (and large) codebases to find the section you are looking for, and it's good to get some practice.

    One implementation of the C library is glibc, which has mirrors on GitHub. I didn't find an official mirror, but an unofficial mirror is at https://github.com/lattera/glibc

    We first look at the math/w_pow.c file which has a promising name. It contains a function __pow which calls __ieee754_pow, which we can find in sysdeps/ieee754/dbl-64/e_pow.c (remember that not all systems are IEEE-754, so it makes sense that the IEEE-754 math code is in its own directory).

    It starts with a few special cases:

    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;
    

    A little farther down you find a branch with a comment

    /* if x<0 */
    

    Which leads us to

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

    So you can see, for negative x and integer y, the glibc version of pow will compute pow(-x,y) and then make the result negative if y is odd.

    This is not the only way to do things, but my guess is that this is common to many implementations. You can see that pow is full of special cases. This is common in library math functions, which are supposed to work correctly with unfriendly inputs like denormals and infinity.

    The pow function is especially hard to read because it is heavily-optimized code which does bit-twiddling on floating-point numbers.

    The C Standard

    The C standard (n1548 §7.12.7.4) has this to say about pow:

    A domain error occurs if x is finite and negative and y is finite and not an integer value.

    So, according to the C standard, negative x should work.

    There is also the matter of appendix F, which gives much tighter constraints on how pow works on IEEE-754 / IEC-60559 systems.

    0 讨论(0)
提交回复
热议问题