08 ноября 2008

Новые функции в Java Math

Если в проекте много "математики" , то код получается не красивый и сложно понимаемый, приходиться писать множество комментариев , что бы было понятно что к чему.
Но SUN идёт на встречу разработчикам, и в каждой новой версии добавляет всё новые функции в основной математический класс - java.lang.Math

Новое в Java 1.5 :

log10(double a) - логарифм с основанием 10
cbrt(double a) - корень кубический
ulp(double d) - на сколько я понял это модуль разницы между параметром d преобразованным в float и ближайшим по возрастанию double значением. поправьте если я не прав!
signum(double d) - возвращает знак числа: 0, если d=0; -1.0 если d<0;>0;
sinh(double x) - \
cosh(double x) - - гиперболические синус, косинус и тангес
tanh(double x)- /
hypot(double x, double y) - гипотенуза
expm1(double x) - = exp(x) - 1
log1p(double x) - = ln(x+1)

Новое в 1.6
copySign(double magnitude, double sign)
getExponent(double d)
nextAfter(double start, double direction)
nextUp(double d)
scalb(double d, int scaleFactor)

Всё красиво, всё удобно - чего только стоит функция гипотенузы. Но всё имеет свои минусы, тут они также есть.
Если сравнить производительность, например той же функции hypot(x,y)
c более привычным способом получения значения гипотенузы Math.sqrt(x*x + y*y), то получим достаточно интересные результаты, а именно - разница в скорости выполнении различается почти в 100 раз!!! причём как это не удивительно , но не в пользу нововведённой функции.
Итак , мой тестовый класс:

public class RealType {
public static void main(String[] s) throws Exception{
long start1;
int counter = 0;
double temp = 0;
for (int k=0; k<5;>
start1 = System.currentTimeMillis();
for (int i=0; i<1000;>
for (int j=0; j<5000;>
temp = Math.sqrt(i*i + j*j);
counter++;
}
}
System.out.println("Spent time to run sqrt " + counter + " times : " + (System.currentTimeMillis() - start1));
counter = 0;
start1 = System.currentTimeMillis();
for (int i=0; i<1000;>
for (int j=0; j<5000;>
temp = Math.hypot(i, j);
counter++;
}
}
System.out.println("Spent time to run hypot " + counter + " times : " + (System.currentTimeMillis() - start1));
}
}
}

После выполнения я получил приблизительно следующие результаты:

Spent time to run sqrt 5000000 times : 63
Spent time to run hypot 5000000 times : 4109
Spent time to run sqrt 5000000 times : 47
Spent time to run hypot 5000000 times : 4172
Spent time to run sqrt 5000000 times : 47
Spent time to run hypot 5000000 times : 4140
Spent time to run sqrt 5000000 times : 47
Spent time to run hypot 5000000 times : 4157
Spent time to run sqrt 5000000 times : 46
Spent time to run hypot 5000000 times : 4172

Тоесть, скорость выполнения одного вызова функции на моём компьютере занимает 800 наносекунд для hypot и 9 наносекунд для sqrt.
Для того что бы понять откуда такая разница, пришлось порытся в исходниках и посмотреть что собой представляет нативная hypot . Поиски привели меня к hypot_e.c и выглядит он следующим образом:

/*----------------------*/


#include "fdlibm.h"

#ifdef __STDC__
double __ieee754_hypot(double x, double y)
#else
double __ieee754_hypot(x,y)
double x, y;
#endif
{
double a=x,b=y,t1,t2,y1,y2,w;
int j,k,ha,hb;

ha = __HI(x)&0x7fffffff; /* high word of x */
hb = __HI(y)&0x7fffffff; /* high word of y */
if(hb > ha) {a=y;b=x;j=ha; ha=hb;hb=j;} else {a=x;b=y;}
__HI(a) = ha; /* a <- |a| */
__HI(b) = hb; /* b <- |b| */
if((ha-hb)>0x3c00000) {return a+b;} /* x/y > 2**60 */
k=0;
if(ha > 0x5f300000) { /* a>2**500 */
if(ha >= 0x7ff00000) { /* Inf or NaN */
w = a+b; /* for sNaN */
if(((ha&0xfffff)|__LO(a))==0) w = a;
if(((hb^0x7ff00000)|__LO(b))==0) w = b;
return w;
}
/* scale a and b by 2**-600 */
ha -= 0x25800000; hb -= 0x25800000; k += 600;
__HI(a) = ha;
__HI(b) = hb;
}
if(hb <>
if(hb <= 0x000fffff) { /* subnormal b or 0 */
if((hb|(__LO(b)))==0) return a;
t1=0;
__HI(t1) = 0x7fd00000; /* t1=2^1022 */
b *= t1;
a *= t1;
k -= 1022;
} else { /* scale a and b by 2^600 */
ha += 0x25800000; /* a *= 2^600 */
hb += 0x25800000; /* b *= 2^600 */
k -= 600;
__HI(a) = ha;
__HI(b) = hb;
}
}
/* medium size a and b */
w = a-b;
if (w>b) {
t1 = 0;
__HI(t1) = ha;
t2 = a-t1;
w = sqrt(t1*t1-(b*(-b)-t2*(a+t1)));
} else {
a = a+a;
y1 = 0;
__HI(y1) = hb;
y2 = b - y1;
t1 = 0;
__HI(t1) = ha+0x00100000;
t2 = a - t1;
w = sqrt(t1*y1-(w*(-w)-(t1*y2+t2*b)));
}
if(k!=0) {
t1 = 1.0;
__HI(t1) += (k<<20);
return t1*w;
} else return w;
}


Теперь всё становится ясным, такое количество дополнительных проверок не могут обойтись дёшево. Совсем другой вопрос всегда ли нужны они! Решать вам...

Комментариев нет: