javamath类中的新功能第1部分实数.docx
- 文档编号:24470451
- 上传时间:2023-05-27
- 格式:DOCX
- 页数:14
- 大小:21.21KB
javamath类中的新功能第1部分实数.docx
《javamath类中的新功能第1部分实数.docx》由会员分享,可在线阅读,更多相关《javamath类中的新功能第1部分实数.docx(14页珍藏版)》请在冰豆网上搜索。
javamath类中的新功能第1部分实数
JavaMath类中的新功能,第1部分实数
有时候您会对一个类熟悉到忘记了它的存在。
如果您能够写出java.lang.Foo的文档,那么Eclipse将帮助您自动完成所需的函数,您无需阅读它的Javadoc。
例如,我使用java.lang.Math(一个我自认为非常了解的类)时就是这样,但令我吃惊的是,我最近偶然读到它的Javadoc——这可能是我近五年来第一次读到,我发现这个类的大小几乎翻了一倍,包含20种我从来没听说过的新方法。
看来我要对它另眼相看了。
Java?
语言规范第5版向java.lang.Math(以及它的姊妹版java.lang.StrictMath)添加了10种新方法,Java6又添加了10种。
在本文中,我重点讨论其中的比较单调的数学函数,如log10和cosh。
在第2部分,我将探讨专为操作浮点数(与抽象实数相反)而设计的函数。
抽象实数(如π或0.2)与Javadouble之间的区别很明显。
首先,数的理想状态是具有无限的精度,而Java表示法把数限制为固定位数。
在处理非常大和非常小的数时,这点很重要。
例如,2,000,000,001(二十亿零一)可以精确表示为一个int,而不是一个float。
最接近的浮点数表示形式是2.0E9—即两亿。
使用double数会更好,因为它们的位数更多(这是应该总是使用double数而不是float数的理由之一);但它们的精度仍然受到一定限制。
计算机算法(Java语言和其他语言的算法)的第二个限制是它基于二进制而不是十进制。
1/5和7/50之类的分数可用十进制精确表示(分别是0.2和0.14),但用二进制表示时,就会出现重复的分数。
如同1/3在用十进制表示时,就会变为0.3333333……以10为基数,任何分母仅包含质数因子5和2的分数都可以精确表示。
以2为基数,则只有分母是2的乘方的分数才可以精确表示:
1/2、1/4、1/8、1/16等。
这种不精确性是迫切需要一个math类的最主要的原因之一。
当然,您可以只使用标准的+和*运算符以及一个简单的循环来定义三角函数和其他使用泰勒级数展开式的函数,如清单1所示:
清单1.使用泰勒级数计算正弦
publicclassSineTaylor{
publicstaticvoidmain(String[]args){
for(doubleangle=0;angle<=4*Math.PI;angle+=Math.PI/8){
System.out.println(degrees(angle)+"\t"+taylorSeriesSine(angle)
+"\t"+Math.sin(angle));
}
}
publicstaticdoubledegrees(doubleradians){
return180*radians/Math.PI;
}
publicstaticdoubletaylorSeriesSine(doubleradians){
doublesine=0;
intsign=1;
for(inti=1;i<40;i+=2){
sine+=Math.pow(radians,i)*sign/factorial(i);
sign*=-1;
}
returnsine;
}
privatestaticdoublefactorial(inti){
doubleresult=1;
for(intj=2;j<=i;j++){
result*=j;
}
returnresult;
}
}
开始运行得不错,只有一点小的误差,如果存在误差的话,也只是最后一位小数不同:
0.00.00.0
22.50.38268343236508970.3826834323650898
45.00.70710678118654750.7071067811865475
67.50.9238795325112870.9238795325112867
90.01.00000000000000021.0
但是,随着角度的增加,误差开始变大,这种简单的方法就不是很适用了:
630.0000000000003-1.0000001371557132-1.0
652.5000000000005-0.9238801080153761-0.9238795325112841
675.0000000000005-0.7071090807463408-0.7071067811865422
697.5000000000006-0.3826922100671368-0.3826834323650824
这里使用泰勒级数得到的结果实际上比我想像的要精确。
但是,随着角度增加到360度、720度(4pi弧度)以及更大时,泰勒级数就逐渐需要更多条件来进行准确计算。
java.lang.Math使用的更加完善的算法就避免了这一点。
泰勒级数的效率也无法与现代桌面芯片的内置正弦函数相比。
要准确快速地计算正弦函数和其他函数,需要非常仔细的算法,专门用于避免无意地将小的误差变成大的错误。
这些算法一般内置在硬件中以更快地执行。
例如,几乎每个在最近10年内组装的X86芯片都具有正弦和余弦函的硬件实现,X86VM只需调用即可,不用基于较原始的运算缓慢地计算它们。
HotSpot利用这些指令显著加速了三角函数的运算。
直角三角形和欧几里德范数
每个高中学生都学过勾股定理:
在直角三角形中,斜边边长的平方等于两条直角边边长平方之和。
即c2=a2+b2
学习过大学物理和高等数学的同学会发现,这个等式会在很多地方出现,不只是在直角三角形中。
例如,R2的平方、二维向量的长度、三角不等式等都存在勾股定理。
(事实上,这些只是看待同一件事情的不同方式。
重点在于勾股定理比看上去要重要得多)。
Java5添加了Math.hypot函数来精确执行这种计算,这也是库很有用的一个出色的实例证明。
原始的简单方法如下:
publicstaticdoublehypot(doublex,doubley){
returnx*x+y*y;
}
实际代码更复杂一些,如清单2所示。
首先应注意的一点是,这是以本机C代码编写的,以使性能最大化。
要注意的第二点是,它尽力使本计算中出现的错误最少。
事实上,应根据x和y的相对大小选择不同的算法。
清单2.实现Math.hypot
的实际代码/*
*====================================================
*Copyright(C)1993bySunMicrosystems,Inc.Allrightsreserved.
*
*DevelopedatSunSoft,aSunMicrosystems,Inc.business.
*Permissiontouse,copy,modify,anddistributethis
*softwareisfreelygranted,providedthatthisnotice
*ispreserved.
*====================================================
*/
#include"fdlibm.h"
#ifdef__STDC__
double__ieee754_hypot(doublex,doubley)
#else
double__ieee754_hypot(x,y)
doublex,y;
#endif
{
doublea=x,b=y,t1,t2,y1,y2,w;
intj,k,ha,hb;
ha=__HI(x)&0x7fffffff;/*highwordofx*/
hb=__HI(y)&0x7fffffff;/*highwordofy*/
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){returna+b;}/*x/y>2**60*/
k=0;
if(ha>0x5f300000){/*a>2**500*/
if(ha>=0x7ff00000){/*InforNaN*/
w=a+b;/*forsNaN*/
if(((ha&0xfffff)|__LO(a))==0)w=a;
if(((hb^0x7ff00000)|__LO(b))==0)w=b;
returnw;
}
/*scaleaandbby2**-600*/
ha-=0x25800000;hb-=0x25800000;k+=600;
__HI(a)=ha;
__HI(b)=hb;
}
if(hb<0x20b00000){/*b<2**-500*/
if(hb<=0x000fffff){/*subnormalbor0*/
if((hb|(__LO(b)))==0)returna;
t1=0;
__HI(t1)=0x7fd00000;/*t1=2^1022*/
b*=t1;
a*=t1;
k-=1022;
}else{/*scaleaandbby2^600*/
ha+=0x25800000;/*a*=2^600*/
hb+=0x25800000;/*b*=2^600*/
k-=600;
__HI(a)=ha;
__HI(b)=hb;
}
}
/*mediumsizeaandb*/
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);
returnt1*w;
}elsereturnw;
}
实际上,是使用这种特定函数,还是几个其他类似函数中的一个取决于平台上的JVM细节。
不过,这种代码很有可能在Sun的标准JDK中调用。
(其他JDK实现可以在必要时改进它。
)
这段代码(以及SunJava开发库中的大多数其他本机数学代码)来自Sun约15年前编写的开源fdlibm库。
该库用于精确实现IEE754浮点数,能进行非常准确的计算,不过会牺牲一些性能。
以10为底的对数
对数说明一个底数的几次幂等于一个给定的值。
也就是说,它是Math.pow()函数的反函数。
以10为底的对数一般出现在工程应用程序中。
以e为底的对数(自然对数)出现在复合计算以及大量科学和数学应用程序中。
以2为底的对数一般出现在算法分析中。
从Java1.0开始,Math类有了一个自然对数。
也就是给定一个参数x,该自然对数返回e的几次幂等于给定的值x。
遗憾的是,Java语言的(以及C、Fortran和Basic的)自然对数函数错误命名为log()。
在我读的每本数学教材中,log都是以10为底的对数,而ln是以e为底的对数,lg是以2为底的对数。
现在已经来不及修复这个问题了,不过Java5添加了一个log10()函数,它是以10为底而不是以e为底的对数。
清单3是一个简单程序,它输出整数1到100的以2、10和e为底的对数:
清单3.1到100的各种底数的对数
publicclassLogarithms{
publicstaticvoidmain(String[]args){
for(inti=1;i<=100;i++){
System.out.println(i+"\t"+
Math.log10(i)+"\t"+
Math.log(i)+"\t"+
lg(i));
}
}
publicstaticdoublelg(doublex){
returnMath.log(x)/Math.log(2.0);
}
}
下面是前10行结果:
10.00.00.0
20.30102999566398120.69314718055994531.0
30.477121254719662441.09861228866810961.584962500721156
40.60205999132796241.38629436111989062.0
50.69897000433601891.60943791243410032.321928094887362
60.77815125038364361.7917594692280552.584962500721156
70.84509804001425681.94591014905531322.807354922057604
80.90308998699194352.07944154167983573.0
90.95424250943932492.19722457733621963.1699250014423126
101.02.3025850929940463.3219280948873626
Math.log10()能正常终止对数函数执行:
0或任何负数的对数返回NaN。
立方根
我不敢说我的生活中曾经需要过立方根,我也不是每天都要使用代数和几何的少数人士之一,更别提偶然涉足微积分、微分方程,甚至抽象代数。
因此,下面这个函数对我毫无用处。
尽管如此,如果意外需要计算立方根,现在就可以了—使用自Java5开始引入的Math.cbrt()方法。
清单4通过计算-5到5之间的整数的立方根进行了演示:
清单4.-5到5的立方根
publicclassCubeRoots{
publicstaticvoidmain(String[]args){
for(inti=-5;i<=5;i++){
System.out.println(Math.cbrt(i));
}
}
}
下面是结果:
-1.709975946676697
-1.5874010519681996
-1.442249*********3
-1.2599210498948732
-1.0
0.0
1.0
1.2599210498948732
1.442249*********3
1.5874010519681996
1.709975946676697
结果显示,与平方根相比,立方根拥有一个不错的特性:
每个实数只有一个实立方根。
这个函数只在其参数为NaN时才返回NaN。
双曲三角函数
双曲三角函数就是对曲线应用三角函数,也就是说,想象将这些点放在笛卡尔平面上来得到t的所有可能值:
x=rcos(t)
y=rsin(t)
您会得到以r为半径的曲线。
相反,假设改用双曲正弦和双曲余弦,如下所示:
x=rcosh(t)
y=rsinh(t)
则会得到一个正交双曲线,原点与它最接近的点之间的距离是r。
还可以这样思考:
其中sin(x)可以写成(eix-e-ix)/2,cos(x)可以写成(eix+e-ix)/2,从这些公式中删除虚数单位后即可得到双曲正弦和双曲余弦,即sinh(x)=(ex-e-x)/2,cosh(x)=(ex+e-x)/2。
Java5添加了所有这三个函数:
Math.cosh()、Math.sinh()和Math.tanh()。
还没有包含反双曲三角函数—反双曲余弦、反双曲正弦和反双曲正切。
实际上,cosh(z)的结果相当于一根吊绳两端相连后得到的形状,即悬链线。
清单5是一个简单的程序,它使用Math.cosh函数绘制一条悬链线:
清单5.使用Math.cosh()绘制悬链线
importjava.awt.*;
publicclassCatenaryextendsFrame{
privatestaticfinalintWIDTH=200;
privatestaticfinalintHEIGHT=200;
privatestaticfinaldoubleMIN_X=-3.0;
privatestaticfinaldoubleMAX_X=3.0;
privatestaticfinaldoubleMAX_Y=8.0;
privatePolygoncatenary=newPolygon();
publicCatenary(Stringtitle){
super(title);
setSize(WIDTH,HEIGHT);
for(doublex=MIN_X;x<=MAX_X;x+=0.1){
doubley=Math.cosh(x);
intscaledX=(int)(x*WIDTH/(MAX_X-MIN_X)+WIDTH/2.0);
intscaledY=(int)(y*HEIGHT/MAX_Y);
//incomputergraphics,yextendsdownratherthanupasin
//Caretesiancoordinates'sowehavetoflip
scaledY=HEIGHT-scaledY;
catenary.addPoint(scaledX,scaledY);
}
}
publicstaticvoidmain(String[]args){
Framef=newCatenary("Catenary");
f.setVisible(true);
}
publicvoidpaint(Graphicsg){
g.drawPolygon(catenary);
}
}
图1为绘制的曲线:
图1.笛卡尔平面中的一条悬链曲线
双曲正弦、双曲余弦和双曲正切函数也会以常见或特殊形式出现在各种计算中。
符号
Math.signum函数将正数转换为1.0,将负数转换为-1.0,0仍然是0。
实际上,它只是提取一个数的符号。
在实现Comparable接口时,这很有用。
一个float和一个double版本可用来维护这种类型。
这个函数的用途很明显,即处理浮点运算、NaN以及正0和负0的特殊情况。
NaN也被当作0,正0和负0应该返回正0和负0。
例如,假设如清单6那样用简单的原始方法实现这个函数:
清单6.存在问题的Math.signum实现
publicstaticdoublesignum(doublex){
if(x==0.0)return0;
elseif(x<0.0)return-1.0;
elsereturn1.0;
}
首先,这个方法会将所有负0转换为正0。
(负0可能不好理解,但它确实是IEEE754规范的必要组成部分)。
其次,它会认为NaN是正的。
实际实现如清单7所示,它更加复杂,而且会仔细处理这些特殊情况:
清单7.实际的、正确的Math.signum实现
publicstaticdoublesignum(doubled){
return(d==0.0||isNaN(d))?
d:
copySign(1.0,d);
}
publicstaticdoublecopySign(doublemagnitude,doublesign){
returnrawCopySign(magnitude,(isNaN(sign)?
1.0d:
sign));
}
publicstaticdoublerawCopySign(doublemagnitude,doublesign){
returnDouble.longBitsToDouble((Double.doubleToRawLongBits(sign)&
(DoubleConsts.SIGN_BIT_MASK))|
(Double.doubleToRawLongBits(magnitude)&
(DoubleConsts.EXP_BIT_MASK|
DoubleConsts.SIGNIF_BIT_MASK)));
}
事半功倍
最有效的代码是从您未编写过的代码。
不要做专家们已经做过的事情。
使用java.lang.Math函数(新的和旧的)的代码将更快、更有效,而且比您自己编写的任何代码都准确。
所以请使用这些函数。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- javamath 中的 新功能 部分 实数