Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Algorithm behind Math.log - Java

Lately I used the Math.log() to code some program and now I ask myself how does the method log() work. How does the computer calculate the logarithm? Thanks for solution.

like image 342
tzuxi Avatar asked May 30 '18 15:05

tzuxi


People also ask

What is the base of Java Math log?

log() method returns the natural logarithm (base e) of a double value as a parameter. There are various cases : If the argument is NaN or less than zero, then the result is NaN. If the argument is positive infinity, then the result is positive infinity.

Is Math log in Java base 2?

lang. Math) is a library which holds the functions to calculate such values, like sin(), cos(), log() etc. But the log() method in Math class calculates the log to the base e. Hence there is no direct method in Java to calculate log to the base 2.


3 Answers

The definition of Math.log from openjdk is:

public static double log(double a) {
    return StrictMath.log(a); // default impl. delegates to StrictMath
}

This lead me to look at the source for StrictMath, where log is declared as:

public static native double log(double a);

The native keyword indicates JNI. In other words, it's off to a libc source to find what we're looking for. Since I use NetBSD, I'll post its definition of log:

/* __ieee754_log(x)
 * Return the logrithm of x
 *
 * Method :
 *   1. Argument Reduction: find k and f such that
 *          x = 2^k * (1+f),
 *     where  sqrt(2)/2 < 1+f < sqrt(2) .
 *
 *   2. Approximation of log(1+f).
 *  Let s = f/(2+f) ; based on log(1+f) = log(1+s) - log(1-s)
 *       = 2s + 2/3 s**3 + 2/5 s**5 + .....,
 *           = 2s + s*R
 *      We use a special Reme algorithm on [0,0.1716] to generate
 *  a polynomial of degree 14 to approximate R The maximum error
 *  of this polynomial approximation is bounded by 2**-58.45. In
 *  other words,
 *              2      4      6      8      10      12      14
 *      R(z) ~ Lg1*s +Lg2*s +Lg3*s +Lg4*s +Lg5*s  +Lg6*s  +Lg7*s
 *      (the values of Lg1 to Lg7 are listed in the program)
 *  and
 *      |      2          14          |     -58.45
 *      | Lg1*s +...+Lg7*s    -  R(z) | <= 2
 *      |                             |
 *  Note that 2s = f - s*f = f - hfsq + s*hfsq, where hfsq = f*f/2.
 *  In order to guarantee error in log below 1ulp, we compute log
 *  by
 *      log(1+f) = f - s*(f - R)    (if f is not too large)
 *      log(1+f) = f - (hfsq - s*(hfsq+R)). (better accuracy)
 *
 *  3. Finally,  log(x) = k*ln2 + log(1+f).
 *              = k*ln2_hi+(f-(hfsq-(s*(hfsq+R)+k*ln2_lo)))
 *     Here ln2 is split into two floating point number:
 *          ln2_hi + ln2_lo,
 *     where n*ln2_hi is always exact for |n| < 2000.
 *
 * Special cases:
 *  log(x) is NaN with signal if x < 0 (including -INF) ;
 *  log(+INF) is +INF; log(0) is -INF with signal;
 *  log(NaN) is that NaN with no signal.
 *
 * Accuracy:
 *  according to an error analysis, the error is always less than
 *  1 ulp (unit in the last place).
 *
 * 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.
 */

#include "math.h"
#include "math_private.h"

static const double
ln2_hi  =  6.93147180369123816490e-01,  /* 3fe62e42 fee00000 */
ln2_lo  =  1.90821492927058770002e-10,  /* 3dea39ef 35793c76 */
two54   =  1.80143985094819840000e+16,  /* 43500000 00000000 */
Lg1 = 6.666666666666735130e-01,  /* 3FE55555 55555593 */
Lg2 = 3.999999999940941908e-01,  /* 3FD99999 9997FA04 */
Lg3 = 2.857142874366239149e-01,  /* 3FD24924 94229359 */
Lg4 = 2.222219843214978396e-01,  /* 3FCC71C5 1D8E78AF */
Lg5 = 1.818357216161805012e-01,  /* 3FC74664 96CB03DE */
Lg6 = 1.531383769920937332e-01,  /* 3FC39A09 D078C69F */
Lg7 = 1.479819860511658591e-01;  /* 3FC2F112 DF3E5244 */

static const double zero   =  0.0;

double
__ieee754_log(double x)
{
    double hfsq,f,s,z,R,w,t1,t2,dk;
    int32_t k,hx,i,j;
    u_int32_t lx;

    EXTRACT_WORDS(hx,lx,x);

    k=0;
    if (hx < 0x00100000) {          /* x < 2**-1022  */
        if (((hx&0x7fffffff)|lx)==0)
        return -two54/zero;     /* log(+-0)=-inf */
        if (hx<0) return (x-x)/zero;    /* log(-#) = NaN */
        k -= 54; x *= two54; /* subnormal number, scale up x */
        GET_HIGH_WORD(hx,x);
    }
    if (hx >= 0x7ff00000) return x+x;
    k += (hx>>20)-1023;
    hx &= 0x000fffff;
    i = (hx+0x95f64)&0x100000;
    SET_HIGH_WORD(x,hx|(i^0x3ff00000)); /* normalize x or x/2 */
    k += (i>>20);
    f = x-1.0;
    if((0x000fffff&(2+hx))<3) { /* |f| < 2**-20 */
        if(f==zero) { if(k==0) return zero;  else {dk=(double)k;
                   return dk*ln2_hi+dk*ln2_lo;}
        }
        R = f*f*(0.5-0.33333333333333333*f);
        if(k==0) return f-R; else {dk=(double)k;
                 return dk*ln2_hi-((R-dk*ln2_lo)-f);}
    }
    s = f/(2.0+f);
    dk = (double)k;
    z = s*s;
    i = hx-0x6147a;
    w = z*z;
    j = 0x6b851-hx;
    t1= w*(Lg2+w*(Lg4+w*Lg6));
    t2= z*(Lg1+w*(Lg3+w*(Lg5+w*Lg7)));
    i |= j;
    R = t2+t1;
    if(i>0) {
        hfsq=0.5*f*f;
        if(k==0) return f-(hfsq-s*(hfsq+R)); else
             return dk*ln2_hi-((hfsq-(s*(hfsq+R)+dk*ln2_lo))-f);
    } else {
        if(k==0) return f-s*(f-R); else
             return dk*ln2_hi-((s*(f-R)-dk*ln2_lo)-f);
    }
}

If you need more help, leave a comment and I'll elaborate.

like image 88
hd1 Avatar answered Oct 17 '22 22:10

hd1


When in doubts, look at the source code (working with Java 8).

public static double log(double a) {
    return StrictMath.log(a); // default impl. delegates to StrictMath
}

You see it calls a static method from StrictMath.java.

static native double log(double a);

What native keyword means you can read here. Basically, it says the method is implemented in another language aside from Java. I have found the folder jdk/src/share/native/java/lang/fdlibm/src/ on GitHub with the math functions. The algorithm is implemented in e_log.c - the rest remains as a magic for me, since the calculation with bites.

like image 40
Nikolas Charalambidis Avatar answered Oct 17 '22 22:10

Nikolas Charalambidis


The log function (and many other functions such as sin(x)) can be approximated with a series: https://en.wikipedia.org/wiki/Natural_logarithm#Series

If you need a good enough approximation (but not full precision), you can stop after the first few iterations of the series. That's how trigonometric functions were approached in early game/demoscene programming.

Look-up tables are a good approach as well, the key here is to find the appropriate distribution of the lookup values (linear may not be ideal in this case).

like image 34
Peter Walser Avatar answered Oct 17 '22 23:10

Peter Walser