日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Java Math 類中的新功能,第 1 部分: 實數

系統 2378 0
在這篇由兩部分組成的文章中,Elliotte Rusty Harold 與您一起探討經典 java.lang.Math 類中的“新”功能。第 1 部分主要討論比較單調的數學函數。第 2 部分將探討專為操作浮點數而設計的函數。

<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

有時候您會對一個類熟悉到忘記了它的存在。如果您能夠寫出 java.lang.Foo 的文檔,那么 Eclipse 將幫助您自動完成所需的函數,您無需閱讀它的 Javadoc。例如,我使用 java.lang.Math (一個我自認為非常了解的類)時就是這樣,但令我吃驚的是,我最近偶然讀到它的 Javadoc —— 這可能是我近五年來第一次讀到,我發現這個類的大小幾乎翻了一倍,包含 20 種我從來沒聽說過的新方法。看來我要對它另眼相看了。


Java? 語言規范第 5 版向 java.lang.Math (以及它的姊妹版 java.lang.StrictMath )添加了 10 種新方法,Java 6 又添加了 10 種。在本文中,我重點討論其中的比較單調的數學函數,如 log10 cosh 。在第 2 部分,我將探討專為操作浮點數(與抽象實數相反)而設計的函數。


抽象實數(如 π 或 0.2)與 Java double 之間的區別很明顯。首先,數的理想狀態是具有無限的精度,而 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. 使用泰勒級數計算正弦

                public class SineTaylor {
            

public static void main(String[] args) {
for (double angle = 0; angle <= 4*Math.PI; angle += Math.PI/8) {
System.out.println(degrees(angle) + "/t" + taylorSeriesSine(angle)
+ "/t" + Math.sin(angle));
}
}

public static double degrees(double radians) {
return 180 * radians/ Math.PI;
}

public static double taylorSeriesSine(double radians) {
double sine = 0;
int sign = 1;
for (int i = 1; i < 40; i+=2) {
sine += Math.pow(radians, i) * sign / factorial(i);
sign *= -1;
}
return sine;
}

private static double factorial(int i) {
double result = 1;
for (int j = 2; j <= i; j++) {
result *= j;
}
return result;
}
}

開始運行得不錯,只有一點小的誤差,如果存在誤差的話,也只是最后一位小數不同:

            0.0       0.0       0.0
            
22.5 0.3826834323650897 0.3826834323650898
45.0 0.7071067811865475 0.7071067811865475
67.5 0.923879532511287 0.9238795325112867
90.0 1.0000000000000002 1.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 度(4 pi 弧度)以及更大時,泰勒級數就逐漸需要更多條件來進行準確計算。 java.lang.Math 使用的更加完善的算法就避免了這一點。

泰勒級數的效率也無法與現代桌面芯片的內置正弦函數相比。要準確快速地計算正弦函數和其他函數,需要非常仔細的算法,專門用于避 免無意地將小的誤差變成大的錯誤。這些算法一般內置在硬件中以更快地執行。例如,幾乎每個在最近 10 年內組裝的 X86 芯片都具有正弦和余弦函的硬件實現,X86 VM 只需調用即可,不用基于較原始的運算緩慢地計算它們。HotSpot 利用這些指令顯著加速了三角函數的運算。

直角三角形和歐幾里德范數

每個高中學生都學過勾股定理:在直角三角形中,斜邊邊長的平方等于兩條直角邊邊長平方之和。即 c 2 = a 2 + b 2

學習過大學物理和高等數學的同學會發現,這個等式會在很多地方出現,不只是在直角三角形中。例如, R 2 的平方、二維向量的長度、三角不等式等都存在勾股定理。(事實上,這些只是看待同一件事情的不同方式。重點在于勾股定理比看上去要重要得多)。

Java 5 添加了 Math.hypot 函數來精確執行這種計算,這也是庫很有用的一個出色的實例證明。原始的簡單方法如下:

            public static double hypot(double x, double y){
            
return x*x + y*y;
}

實際代碼更復雜一些,如清單 2 所示。首先應注意的一點是,這是以本機 C 代碼編寫的,以使性能最大化。要注意的第二點是,它盡力使本計算中出現的錯誤最少。事實上,應根據 x y 的相對大小選擇不同的算法。


清單 2. 實現 Math.hypot
            				 的實際代碼/*
            
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/

#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 < 0x20b00000) { /* b < 2**-500 */
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;
}

實際上,是使用這種特定函數,還是幾個其他類似函數中的一個取決于平臺上的 JVM 細節。不過,這種代碼很有可能在 Sun 的標準 JDK 中調用。(其他 JDK 實現可以在必要時改進它。)

這段代碼(以及 Sun Java 開發庫中的大多數其他本機數學代碼)來自 Sun 約 15 年前編寫的開源 fdlibm 庫。該庫用于精確實現 IEE754 浮點數,能進行非常準確的計算,不過會犧牲一些性能。







以 10 為底的對數

對數說明一個底數的幾次冪等于一個給定的值。也就是說,它是 Math.pow() 函數的反函數。以 10 為底的對數一般出現在工程應用程序中。以 e 為底的對數(自然對數)出現在復合計算以及大量科學和數學應用程序中。以 2 為底的對數一般出現在算法分析中。

從 Java 1.0 開始, Math 類有了一個自然對數。也就是給定一個參數 x,該自然對數返回 e 的幾次冪等于給定的值 x。遺憾的是,Java 語言的(以及 C 、Fortran 和 Basic 的)自然對數函數錯誤命名為 log() 。在我讀的每本數學教材中,log 都是以 10 為底的對數,而 ln 是以 e 為底的對數,lg 是以 2 為底的對數。現在已經來不及修復這個問題了,不過 Java 5 添加了一個 log10() 函數,它是以 10 為底而不是以 e 為底的對數。

清單 3 是一個簡單程序,它輸出整數 1 到 100 的以 2、10 和 e 為底的對數:


清單 3. 1 到 100 的各種底數的對數
            public class Logarithms {
            
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
System.out.println(i + "/t" +
Math.log10(i) + "/t" +
Math.log(i) + "/t" +
lg(i));
}
}

public static double lg(double x) {
return Math.log(x)/Math.log(2.0);
}
}

下面是前 10 行結果:

            1    0.0                                0.0                                 0.0
            
2 0.3010299956639812 0.6931471805599453 1.0
3 0.47712125471966244 1.0986122886681096 1.584962500721156
4 0.6020599913279624 1.3862943611198906 2.0
5 0.6989700043360189 1.6094379124341003 2.321928094887362
6 0.7781512503836436 1.791759469228055 2.584962500721156
7 0.8450980400142568 1.9459101490553132 2.807354922057604
8 0.9030899869919435 2.0794415416798357 3.0
9 0.9542425094393249 2.1972245773362196 3.1699250014423126
10 1.0 2.302585092994046 3.3219280948873626

Math.log10() 能正常終止對數函數執行:0 或任何負數的對數返回 NaN。








立方根


我不敢說我的生活中曾經需要過立方根,我也不是每天都要使用代數和幾何的少數人士之一,更別提偶然涉足微積分、微分方程,甚至抽象代數。因此,下面這個函數對我毫無用處。盡管如此,如果意外需要計算立方根,現在就可以了 — 使用自 Java 5 開始引入的 Math.cbrt() 方法。清單 4 通過計算 -5 到 5 之間的整數的立方根進行了演示:


清單 4. -5 到 5 的立方根
            				public class CubeRoots {
            
public static void main(String[] args) {
for (int i = -5; i <= 5; i++) {
System.out.println(Math.cbrt(i));
}
}
}

下面是結果:

            -1.709975946676697
            
-1.5874010519681996
-1.4422495703074083
-1.2599210498948732
-1.0
0.0
1.0
1.2599210498948732
1.4422495703074083
1.5874010519681996
1.709975946676697

結果顯示,與平方根相比,立方根擁有一個不錯的特性:每個實數只有一個實立方根。這個函數只在其參數為 NaN 時才返回 NaN。








雙曲三角函數

雙曲三角函數就是對曲線應用三角函數,也就是說,想象將這些點放在笛卡爾平面上來得到 t 的所有可能值:

            x = r cos(t)
            
y = r sin(t)

您會得到以 r 為半徑的曲線。相反,假設改用雙曲正弦和雙曲余弦,如下所示:

            x = r cosh(t)
            
y = r sinh(t)

則會得到一個正交雙曲線,原點與它最接近的點之間的距離是 r。

還可以這樣思考:其中 sin(x) 可以寫成 ( e i x - e -i x )/2,cos(x) 可以寫成 ( e i x + e -i x )/2,從這些公式中刪除虛數單位后即可得到雙曲正弦和雙曲余弦,即 sinh(x) = ( e x - e -x )/2,cosh(x) = ( e x + e -x )/2。

Java 5 添加了所有這三個函數: Math.cosh() Math.sinh() Math.tanh() 。還沒有包含反雙曲三角函數 — 反雙曲余弦、反雙曲正弦和反雙曲正切。

實際上,cosh(z) 的結果相當于一根吊繩兩端相連后得到的形狀,即 懸鏈線 。清單 5 是一個簡單的程序,它使用 Math.cosh 函數繪制一條懸鏈線:


清單 5. 使用 Math.cosh() 繪制懸鏈線

            import java.awt.*;
            

public class Catenary extends Frame {

private static final int WIDTH = 200;
private static final int HEIGHT = 200;
private static final double MIN_X = -3.0;
private static final double MAX_X = 3.0;
private static final double MAX_Y = 8.0;

private Polygon catenary = new Polygon();

public Catenary(String title) {
super(title);
setSize(WIDTH, HEIGHT);
for (double x = MIN_X; x <= MAX_X; x += 0.1) {
double y = Math.cosh(x);
int scaledX = (int) (x * WIDTH/(MAX_X - MIN_X) + WIDTH/2.0);
int scaledY = (int) (y * HEIGHT/MAX_Y);
// in computer graphics, y extends down rather than up as in
// Caretesian coordinates' so we have to flip
scaledY = HEIGHT - scaledY;
catenary.addPoint(scaledX, scaledY);
}
}

public static void main(String[] args) {
Frame f = new Catenary("Catenary");
f.setVisible(true);
}

public void paint(Graphics g) {
g.drawPolygon(catenary);
}

}

圖 1 為繪制的曲線:


圖 1. 笛卡爾平面中的一條懸鏈曲線
Java Math 類中的新功能,第 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 實現
            public static double signum(double x) {
            
if (x == 0.0) return 0;
else if (x < 0.0) return -1.0;
else return 1.0;
}

首先,這個方法會將所有負 0 轉換為正 0。(負 0 可能不好理解,但它確實是 IEEE 754 規范的必要組成部分)。其次,它會認為 NaN 是正的。實際實現如清單 7 所示,它更加復雜,而且會仔細處理這些特殊情況:


清單 7. 實際的、正確的 Math.signum 實現
            public static double signum(double d) {
            
return (d == 0.0 || isNaN(d))?d:copySign(1.0, d);
}

public static double copySign(double magnitude, double sign) {
return rawCopySign(magnitude, (isNaN(sign)?1.0d:sign));
}

public static double rawCopySign(double magnitude, double sign) {
return Double.longBitsToDouble((Double.doubleToRawLongBits(sign) &
(DoubleConsts.SIGN_BIT_MASK)) |
(Double.doubleToRawLongBits(magnitude) &
(DoubleConsts.EXP_BIT_MASK |
DoubleConsts.SIGNIF_BIT_MASK)));
}








事半功倍

最有效的代碼是從您未編寫過的代碼。不要做專家們已經做過的事情。使用 java.lang.Math 函數(新的和舊的)的代碼將更快、更有效,而且比您自己編寫的任何代碼都準確。所以請使用這些函數。



參考資料


學習


獲得產品和技術
  • fdlibm :一個適用于支持 IEEE 754 浮點數的機器的 C math 庫,可在 Netlib 數學軟件庫中找到。

  • OpenJDK :查看此開源 Java SE 實現中 math 類的源代碼。


關于作者

Elliotte Rusty Harold 的照片

Elliotte Rusty Harold 出生在新奧爾良,現在他還定期回老家喝一碗秋葵湯。他與他的妻子 Beth、寵物貓 Charm(以 quark 命名)和 Marjorie(以他岳母的名字命名)住在 Irvine 附近的大學城中心。他的 Cafe au Lait Web 站點已成為 Internet 上最流行的獨立 Java 站點之一,而且其姊妹站點 Cafe con Leche 已經是最流行的 XML 站點之一。他最近的著作是 Refactoring HTML


Java Math 類中的新功能,第 1 部分: 實數


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 简阳市| 芮城县| 五台县| 蒲城县| 大关县| 万全县| 四子王旗| 乌鲁木齐市| 霍邱县| 富蕴县| 衢州市| 卢湾区| 乐至县| 社旗县| 梅州市| 余姚市| 龙江县| 平罗县| 宁远县| 石首市| 临西县| 德格县| 靖边县| 邹平县| 海阳市| 祥云县| 利辛县| 靖西县| 凤山县| 西充县| 资中县| 汕尾市| 桂林市| 故城县| 锡林郭勒盟| 徐水县| 额尔古纳市| 杭锦后旗| 枞阳县| 焉耆| 泰来县|