Где я могу найти исходный код функции квадратного корня Java?

Я знаю, что Math.sqrt звонит StrictMath.sqrt(double a).

Сигнатура метода в классе StrictMath:

public static native double sqrt(double a);

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


person Community    schedule 05.05.2009    source источник
comment
Я думал, что у Intel есть инструкция sqrt - это не так?   -  person Marichyasana    schedule 12.07.2013


Ответы (4)


При установке JDK исходный код стандартной библиотеки можно найти внутри src.zip. Однако это не поможет вам для StrictMath, так как StrictMath.sqrt(double) реализовано следующим образом:

public static native double sqrt(double a);

Так что на самом деле это просто нативный вызов, и Java может по-разному реализовывать его на разных платформах.

Однако, как указано в документации StrictMath:

Чтобы помочь обеспечить переносимость программ Java, определения некоторых числовых функций в этом пакете требуют, чтобы они давали те же результаты, что и некоторые опубликованные алгоритмы. Эти алгоритмы доступны в известной сетевой библиотеке netlib в виде пакета Freely Distributable Math Library, fdlibm. . Эти алгоритмы, написанные на языке программирования C, следует понимать как выполняемые со всеми операциями с плавающей запятой в соответствии с правилами арифметики Java с плавающей запятой.

Математическая библиотека Java определена относительно fdlibm версии 5.3. Если fdlibm предоставляет более одного определения функции (например, acos), используйте версию базовой функции IEEE 754 (находящуюся в файле, имя которого начинается с буквы e). Семантика fdlibm требует следующих методов: sin, cos, tan, asin, acos, atan, exp, log, log10, cbrt, atan2, pow, sinh, cosh, tanh, hypot, expm1 и log1p.

Таким образом, найдя соответствующую версию исходного кода fdlibm, вы также должны найти точную реализацию, используемую Java (и указанную здесь спецификацию).

Реализация, используемая fdlibm,

static const double one = 1.0, tiny=1.0e-300;

double z;
int sign = (int) 0x80000000; 
unsigned r, t1, s1, ix1, q1;
int ix0, s0, q, m, t, i;

ix0 = __HI(x); /* high word of x */
ix1 = __LO(x); /* low word of x */

/* take care of Inf and NaN */
if ((ix0 & 0x7ff00000) == 0x7ff00000) {            
    return x*x+x; /* sqrt(NaN) = NaN, 
                     sqrt(+inf) = +inf,
                     sqrt(-inf) = sNaN */
} 

/* take care of zero */
if (ix0 <= 0) {
    if (((ix0&(~sign)) | ix1) == 0) {
        return x; /* sqrt(+-0) = +-0 */
    } else if (ix0 < 0) {
        return (x-x) / (x-x); /* sqrt(-ve) = sNaN */
    }
}

/* normalize x */
m = (ix0 >> 20);
if (m == 0) { /* subnormal x */
    while (ix0==0) {
        m -= 21;
        ix0 |= (ix1 >> 11); ix1 <<= 21;
    }
    for (i=0; (ix0&0x00100000)==0; i++) {
        ix0 <<= 1;
    }
    m -= i-1;
    ix0 |= (ix1 >> (32-i));
    ix1 <<= i;
}

m -= 1023; /* unbias exponent */
ix0 = (ix0&0x000fffff)|0x00100000;
if (m&1) { /* odd m, double x to make it even */
    ix0 += ix0 + ((ix1&sign) >> 31);
    ix1 += ix1;
}

m >>= 1; /* m = [m/2] */

/* generate sqrt(x) bit by bit */
ix0 += ix0 + ((ix1 & sign)>>31);
ix1 += ix1;
q = q1 = s0 = s1 = 0; /* [q,q1] = sqrt(x) */
r = 0x00200000; /* r = moving bit from right to left */

while (r != 0) {
    t = s0 + r; 
    if (t <= ix0) { 
        s0 = t+r; 
        ix0 -= t; 
        q += r; 
    } 
    ix0 += ix0 + ((ix1&sign)>>31);
    ix1 += ix1;
    r>>=1;
}

r = sign;
while (r != 0) {
    t1 = s1+r; 
    t = s0;
    if ((t<ix0) || ((t == ix0) && (t1 <= ix1))) { 
        s1 = t1+r;
        if (((t1&sign) == sign) && (s1 & sign) == 0) {
            s0 += 1;
        }
        ix0 -= t;
        if (ix1 < t1) {
            ix0 -= 1;
        }
        ix1 -= t1;
        q1  += r;
    }
    ix0 += ix0 + ((ix1&sign) >> 31);
    ix1 += ix1;
    r >>= 1;
}

/* use floating add to find out rounding direction */
if((ix0 | ix1) != 0) {
    z = one - tiny; /* trigger inexact flag */
    if (z >= one) {
        z = one+tiny;
        if (q1 == (unsigned) 0xffffffff) { 
            q1=0; 
            q += 1;
        }
    } else if (z > one) {
        if (q1 == (unsigned) 0xfffffffe) {
            q+=1;
        }
        q1+=2; 
    } else
        q1 += (q1&1);
    }
}

ix0 = (q>>1) + 0x3fe00000;
ix1 =  q 1>> 1;
if ((q&1) == 1) ix1 |= sign;
ix0 += (m <<20);
__HI(z) = ix0;
__LO(z) = ix1;
return z;
person Joey    schedule 05.05.2009
comment
Мммм. Это почти слишком просто :-) - person Brian Agnew; 05.05.2009
comment
Я просто не понимаю, как это реализовано без циклов переменной длины, лол. Вы знаете, где найти этому объяснение? - person Gershy; 03.02.2017
comment
@GershomMaes: я бы, наверное, спросил о том, как работает алгоритм на одном из математических сайтов StackExchange. Комментарии не для вопросов. - person Joey; 03.02.2017
comment
Семантика fdlibm требует следующих методов: sin, cos, tan, asin, acos, atan, exp, log, log10, cbrt, atan2, pow, sinh, cosh, tanh, hypot, expm1 и log1p. - так sqrt не один из них? - person eis; 23.01.2021

Так как у меня завалялся OpenJDK, я покажу его реализацию здесь.

В jdk/src/share/native/java/lang/StrictMath.c:

JNIEXPORT jdouble JNICALL
Java_java_lang_StrictMath_sqrt(JNIEnv *env, jclass unused, jdouble d)
{
    return (jdouble) jsqrt((double)d);
}

jsqrt определяется как sqrt в jdk/src/share/native/java/lang/fdlibm/src/w_sqrt.c (имя меняется через препроцессор):

#ifdef __STDC__
        double sqrt(double x)           /* wrapper sqrt */
#else
        double sqrt(x)                  /* wrapper sqrt */
        double x;
#endif
{
#ifdef _IEEE_LIBM
        return __ieee754_sqrt(x);
#else
        double z;
        z = __ieee754_sqrt(x);
        if(_LIB_VERSION == _IEEE_ || isnan(x)) return z;
        if(x<0.0) {
            return __kernel_standard(x,x,26); /* sqrt(negative) */
        } else
            return z;
#endif
}

И __ieee754_sqrt определяется в jdk/src/share/native/java/lang/fdlibm/src/e_sqrt.c как:

#ifdef __STDC__
static  const double    one     = 1.0, tiny=1.0e-300;
#else
static  double  one     = 1.0, tiny=1.0e-300;
#endif

#ifdef __STDC__
        double __ieee754_sqrt(double x)
#else
        double __ieee754_sqrt(x)
        double x;
#endif
{
        double z;
        int     sign = (int)0x80000000;
        unsigned r,t1,s1,ix1,q1;
        int ix0,s0,q,m,t,i;

        ix0 = __HI(x);                  /* high word of x */
        ix1 = __LO(x);          /* low word of x */

    /* take care of Inf and NaN */
        if((ix0&0x7ff00000)==0x7ff00000) {
            return x*x+x;               /* sqrt(NaN)=NaN, sqrt(+inf)=+inf
                                           sqrt(-inf)=sNaN */
        }
    /* take care of zero */
        if(ix0<=0) {
            if(((ix0&(~sign))|ix1)==0) return x;/* sqrt(+-0) = +-0 */
            else if(ix0<0)
                return (x-x)/(x-x);             /* sqrt(-ve) = sNaN */
        }
    /* normalize x */
        m = (ix0>>20);
        if(m==0) {                              /* subnormal x */
            while(ix0==0) {
                m -= 21;
                ix0 |= (ix1>>11); ix1 <<= 21;
            }
            for(i=0;(ix0&0x00100000)==0;i++) ix0<<=1;
            m -= i-1;
            ix0 |= (ix1>>(32-i));
            ix1 <<= i;
        }
        m -= 1023;      /* unbias exponent */
        ix0 = (ix0&0x000fffff)|0x00100000;
        if(m&1){        /* odd m, double x to make it even */
            ix0 += ix0 + ((ix1&sign)>>31);
            ix1 += ix1;
        }
        m >>= 1;        /* m = [m/2] */

    /* generate sqrt(x) bit by bit */
        ix0 += ix0 + ((ix1&sign)>>31);
        ix1 += ix1;
        q = q1 = s0 = s1 = 0;   /* [q,q1] = sqrt(x) */
        r = 0x00200000;         /* r = moving bit from right to left */

        while(r!=0) {
            t = s0+r;
            if(t<=ix0) {
                s0   = t+r;
                ix0 -= t;
                q   += r;
            }
            ix0 += ix0 + ((ix1&sign)>>31);
            ix1 += ix1;
            r>>=1;
        }

        r = sign;
        while(r!=0) {
            t1 = s1+r;
            t  = s0;
            if((t<ix0)||((t==ix0)&&(t1<=ix1))) {
                s1  = t1+r;
                if(((t1&sign)==sign)&&(s1&sign)==0) s0 += 1;
                ix0 -= t;
                if (ix1 < t1) ix0 -= 1;
                ix1 -= t1;
                q1  += r;
            }
            ix0 += ix0 + ((ix1&sign)>>31);
            ix1 += ix1;
            r>>=1;
        }

    /* use floating add to find out rounding direction */
        if((ix0|ix1)!=0) {
            z = one-tiny; /* trigger inexact flag */
            if (z>=one) {
                z = one+tiny;
                if (q1==(unsigned)0xffffffff) { q1=0; q += 1;}
                else if (z>one) {
                    if (q1==(unsigned)0xfffffffe) q+=1;
                    q1+=2;
                } else
                    q1 += (q1&1);
            }
        }
        ix0 = (q>>1)+0x3fe00000;
        ix1 =  q1>>1;
        if ((q&1)==1) ix1 |= sign;
        ix0 += (m <<20);
        __HI(z) = ix0;
        __LO(z) = ix1;
        return z;
}

В файле есть обширные комментарии, объясняющие используемые методы, которые я опустил для (полу)краткости. Вот файл в Mercurial (надеюсь, это правильный способ ссылки на него).

person Michael Myers    schedule 05.05.2009
comment
плюс один за ссылку на источник в hg - person Will; 14.06.2013

Загрузите исходный код из OpenJDK.

person Mnementh    schedule 05.05.2009

Я точно не знаю, но я думаю, что вы найдете алгоритм Ньютона в конечной точке.

UPD: как говорится в комментариях, конкретная реализация зависит от конкретной Java-машины. Для окон, вероятно, используется реализация на ассемблере, где существует стандартный оператор sqrt.

person Roman    schedule 05.05.2009
comment
Коды операций ассемблера не зависят от ОС, так что это не имеет ничего общего с Windows. Но да, JVM предпочтет нативную инструкцию, как подробно описано в различных комментариях исходного кода C. - person Joey; 05.05.2009