熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

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

2022-06-13   來源: Java核心技術 

  摘要在這篇由兩部分組成的文章中Elliotte Rusty Harold 與您一起探討經典 javalangMath 類中的功能 部分主要討論比較單調的數學函數 部分將探討專為操作浮點數而設計的函數

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

  Java&#;語言規范第 版向 javalangMath(以及它的姊妹版 javalangStrictMath)添加了 種新方法Java 又添加了 在本文中我重點討論其中的比較單調的數學函數如 log 和 cosh在第 部分我將探討專為操作浮點數(與抽象實數相反)而設計的函數

  抽象實數(如 π 或 )與 Java double 之間的區別很明顯首先數的理想狀態是具有無限的精度而 Java 表示法把數限制為固定位數在處理非常大和非常小的數時這點很重要例如(二十億零一)可以精確表示為一個 int而不是一個 float最接近的浮點數表示形式是 E — 即兩億使用 double 數會更好因為它們的位數更多(這是應該總是使用 double 數而不是 float 數的理由之一)但它們的精度仍然受到一定限制

  計算機算法(Java 語言和其他語言的算法)的第二個限制是它基於二進制而不是十進制// 之類的分數可用十進制精確表示(分別是 但用二進制表示時就會出現重復的分數如同 / 在用十進制表示時就會變為 ……以 為基數任何分母僅包含質數因子 的分數都可以精確表示 為基數則只有分母是 的乘方的分數才可以精確表示////

  這種不精確性是迫切需要一個 math 類的最主要的原因之一當然您可以只使用標准的 + 和 * 運算符以及一個簡單的循環來定義三角函數和其他使用泰勒級數展開式的函數如清單 所示

  清單 使用泰勒級數計算正弦

   public class SineTaylor {

    public static void main(String[] args) {
        for (double angle = ; angle <= *MathPI; angle += MathPI/) {
            Systemoutprintln(degrees(angle) + \t + taylorSeriesSine(angle)
                    + \t + Mathsin(angle));
        }
    }
   
    public static double degrees(double radians) {
        return * radians/ MathPI;
    }
   
    public static double taylorSeriesSine(double radians) {      
        double sine = ;
        int sign = ;
        for (int i = ; i < ; i+=) {
            sine += Mathpow(radians i) * sign / factorial(i);
            sign *= ;
        }
        return sine;
    }

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

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

                
             
             
              
             

  但是隨著角度的增加誤差開始變大這種簡單的方法就不是很適用了

                
             
             
             

  這裡使用泰勒級數得到的結果實際上比我想像的要精確但是隨著角度增加到 度( pi 弧度)以及更大時泰勒級數就逐漸需要更多條件來進行准確計算javalangMath 使用的更加完善的算法就避免了這一點

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

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

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

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

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

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

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

  清單 實現 Mathhypot

   的實際代碼/*
* ====================================================
* Copyright (C) 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 fdlibmh

#ifdef __STDC__
       double __ieee_hypot(double x double y)
#else
       double __ieee_hypot(xy)
       double x y;
#endif
{
       double a=xb=yttyyw;
       int jkhahb;

       ha = __HI(x)&xfffffff;       /* high word of  x */
       hb = __HI(y)&xfffffff;       /* 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((hahb)>xc) {return a+b;} /* x/y > ** */
       k=;
       if(ha > xf) {       /* a>** */
          if(ha >= xff) {       /* Inf or NaN */
              w = a+b;                     /* for sNaN */
              if(((ha&xfffff)|__LO(a))==) w = a;
              if(((hb^xff)|__LO(b))==) w = b;
              return w;
          }
          /* scale a and b by ** */
          ha = x; hb = x;       k += ;
          __HI(a) = ha;
          __HI(b) = hb;
       }
       if(hb < xb) {       /* b < ** */
           if(hb <= xfffff) {       /* subnormal b or */      
              if((hb|(__LO(b)))==) return a;
              t=;
              __HI(t) = xfd;       /* t=^ */
              b *= t;
              a *= t;
              k = ;
           } else {              /* scale a and b by ^ */
               ha += x;        /* a *= ^ */
              hb += x;       /* b *= ^ */
              k = ;
                 __HI(a) = ha;
                 __HI(b) = hb;
           }
       }
    /* medium size a and b */
       w = ab;
       if (w>b) {
           t = ;
           __HI(t) = ha;
           t = at;
           w  = sqrt(t*t(b*(b)t*(a+t)));
       } else {
           a  = a+a;
           y = ;
           __HI(y) = hb;
           y = b y;
           t = ;
           __HI(t) = ha+x;
           t = a t;
           w  = sqrt(t*y(w*(w)(t*y+t*b)));
       }
       if(k!=) {
           t = ;
           __HI(t) += (k<<);
           return t*w;
       } else return w;
}

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

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

  為底的對數

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

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

  清單 是一個簡單程序它輸出整數 的以 和 e 為底的對數

  清單 的各種底數的對數

  

  public static void main(String[] args) {
        for (int i = ; i <= ; i++) {
            Systemoutprintln(i + \t +
                              Mathlog(i) + \t +
                              Mathlog(i) + \t  +
                              lg(i));
        }
    }

  public static double lg(double x) {
        return Mathlog(x)/Mathlog();
    }
}

  下面是前 行結果

                                                                       
        
     
        
        
          
        
        
        
                                      

  Mathlog() 能正常終止對數函數執行 或任何負數的對數返回 NaN

  立方根

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

  清單 的立方根

   public class CubeRoots {
    public static void main(String[] args) {
        for (int i = ; i <= ; i++) {
            Systemoutprintln(Mathcbrt(i));
        }
    }
}

  下面是結果

  









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

  雙曲三角函數

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

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

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

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

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

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

  Java 添加了所有這三個函數sh()Mathsinh() 和 Mathtanh()還沒有包含反雙曲三角函數 — 反雙曲余弦反雙曲正弦和反雙曲正切

  實際上cosh(z) 的結果相當於一根吊繩兩端相連後得到的形狀即懸鏈線清單 是一個簡單的程序它使用 sh 函數繪制一條懸鏈線

  清單 使用 sh() 繪制懸鏈線

  

  import javaawt*;

  public class Catenary extends Frame {

  private static final int WIDTH = ;
    private static final int HEIGHT = ;
    private static final double MIN_X = ;
    private static final double MAX_X = ;
    private static final double MAX_Y = ;

  private Polygon catenary = new Polygon();

  public Catenary(String title) {
        super(title);
        setSize(WIDTH HEIGHT);
        for (double x = MIN_X; x <= MAX_X; x += ) {
            double y = sh(x);
            int scaledX = (int) (x * WIDTH/(MAX_X MIN_X) + WIDTH/);
            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;
            catenaryaddPoint(scaledX scaledY);
        }
    }

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

  public void paint(Graphics g) {
        gdrawPolygon(catenary);
    }

  }

   為繪制的曲線

   笛卡爾平面中的一條懸鏈曲線

  

  雙曲正弦雙曲余弦和雙曲正切函數也會以常見或特殊形式出現在各種計算中

  符號

  Mathsignum 函數將正數轉換為 將負數轉換為 仍然是 實際上它只是提取一個數的符號在實現 Comparable 接口時這很有用

  一個 float 和一個 double 版本可用來維護這種類型 這個函數的用途很明顯即處理浮點運算NaN 以及正 和負 的特殊情況NaN 也被當作 和負 應該返回正 和 負 例如假設如清單 那樣用簡單的原始方法實現這個函數

  清單 存在問題的 Mathsignum 實現

   public static double signum(double x) {
  if (x == ) return ;
  else if (x < ) return ;
  else return ;
}

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

  清單 實際的正確的 Mathsignum 實現

  

  public static double signum(double d) {
    return (d == || isNaN(d))?d:copySign( d);
}

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

  public static double rawCopySign(double magnitude double sign) {
    return DoublelongBitsToDouble((DoubledoubleToRawLongBits(sign) &
                                   (DoubleConstsSIGN_BIT_MASK)) |
                                   (DoubledoubleToRawLongBits(magnitude) &
                                   (DoubleConstsEXP_BIT_MASK |
                                    DoubleConstsSIGNIF_BIT_MASK)));
}

  事半功倍

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

  參考資料

  您可以參閱本文在 developerWorks 全球網站上的 英文原文

  類型值和變量Java 語言規范的第 章討論了浮點運算

  二進制浮點運算的 IEEE 標准IEEE 標准定義了大多數現代處理器和語言(包括 Java 語言)中的浮點運算

  javalangMath 提供本文所討論函數的類的 Javadoc

  Bug 不滿足的用戶要求 JDK 中包含更快的三角函數

  關於作者

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


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26110.html
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.