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 { |
開始運行得不錯,只有一點小的誤差,如果存在誤差的話,也只是最后一位小數不同:
0.0 0.0 0.0 |
但是,隨著角度的增加,誤差開始變大,這種簡單的方法就不是很適用了:
630.0000000000003 -1.0000001371557132 -1.0 |
這里使用泰勒級數得到的結果實際上比我想像的要精確。但是,隨著角度增加到 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){ |
實際代碼更復雜一些,如清單 2 所示。首先應注意的一點是,這是以本機 C 代碼編寫的,以使性能最大化。要注意的第二點是,它盡力使本計算中出現的錯誤最少。事實上,應根據
x
和
y
的相對大小選擇不同的算法。
清單 2. 實現
Math.hypot
的實際代碼/* |
實際上,是使用這種特定函數,還是幾個其他類似函數中的一個取決于平臺上的 JVM 細節。不過,這種代碼很有可能在 Sun 的標準 JDK 中調用。(其他 JDK 實現可以在必要時改進它。)
這段代碼(以及 Sun Java 開發庫中的大多數其他本機數學代碼)來自 Sun 約 15 年前編寫的開源
fdlibm
庫。該庫用于精確實現 IEE754 浮點數,能進行非常準確的計算,不過會犧牲一些性能。
![]() ![]() |
![]()
|
對數說明一個底數的幾次冪等于一個給定的值。也就是說,它是
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 { |
下面是前 10 行結果:
1 0.0 0.0 0.0 |
Math.log10()
能正常終止對數函數執行:0 或任何負數的對數返回 NaN。
![]() ![]() |
![]()
|
我不敢說我的生活中曾經需要過立方根,我也不是每天都要使用代數和幾何的少數人士之一,更別提偶然涉足微積分、微分方程,甚至抽象代數。因此,下面這個函數對我毫無用處。盡管如此,如果意外需要計算立方根,現在就可以了 — 使用自 Java 5 開始引入的
Math.cbrt()
方法。清單 4 通過計算 -5 到 5 之間的整數的立方根進行了演示:
清單 4. -5 到 5 的立方根
public class CubeRoots { |
下面是結果:
-1.709975946676697 |
結果顯示,與平方根相比,立方根擁有一個不錯的特性:每個實數只有一個實立方根。這個函數只在其參數為 NaN 時才返回 NaN。
![]() ![]() |
![]()
|
雙曲三角函數就是對曲線應用三角函數,也就是說,想象將這些點放在笛卡爾平面上來得到 t 的所有可能值:
x = r cos(t) |
您會得到以 r 為半徑的曲線。相反,假設改用雙曲正弦和雙曲余弦,如下所示:
x = r cosh(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.*; |
圖 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
實現
public static double signum(double x) { |
首先,這個方法會將所有負 0 轉換為正 0。(負 0 可能不好理解,但它確實是 IEEE 754 規范的必要組成部分)。其次,它會認為 NaN 是正的。實際實現如清單 7 所示,它更加復雜,而且會仔細處理這些特殊情況:
清單 7. 實際的、正確的
Math.signum
實現
public static double signum(double d) { |
![]() ![]() |
![]()
|
最有效的代碼是從您未編寫過的代碼。不要做專家們已經做過的事情。使用
java.lang.Math
函數(新的和舊的)的代碼將更快、更有效,而且比您自己編寫的任何代碼都準確。所以請使用這些函數。
學習
-
類型、值和變量
:Java 語言規范的第 4 章討論了浮點運算。
-
二進制浮點運算的 IEEE 標準
:IEEE 754 標準定義了大多數現代處理器和語言(包括 Java 語言)中的浮點運算。
-
java.lang.Math
:提供本文所討論函數的類的 Javadoc。
-
Bug 5005861
:不滿足的用戶要求 JDK 中包含更快的三角函數。
-
懸鏈線
:Wikipedia 說明了懸鏈線的歷史及其背后的數學理論。
獲得產品和技術
-
fdlibm
:一個適用于支持 IEEE 754 浮點數的機器的 C math 庫,可在 Netlib 數學軟件庫中找到。
-
OpenJDK
:查看此開源 Java SE 實現中 math 類的源代碼。
![]() |
||
![]() |
Elliotte Rusty Harold 出生在新奧爾良,現在他還定期回老家喝一碗秋葵湯。他與他的妻子 Beth、寵物貓 Charm(以 quark 命名)和 Marjorie(以他岳母的名字命名)住在 Irvine 附近的大學城中心。他的 Cafe au Lait Web 站點已成為 Internet 上最流行的獨立 Java 站點之一,而且其姊妹站點 Cafe con Leche 已經是最流行的 XML 站點之一。他最近的著作是 Refactoring HTML 。 |
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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