熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

Java理論與實踐:您的小數點在哪?

2013-11-15 11:46:53  來源: JSP教程 

  Java理論與實踐您的小數點在哪?
          ——使用浮點數和小數中的技巧和陷阱
  作者Brian Goetz 本文選自IBM DW中國網站
    許多程序員在其整個開發生涯中都不曾使用定點或浮點數可能的例外是偶爾在計時測試或基准測試程序中會用到Java 語言和類庫支持兩類非整數類型 — IEEE 浮點(float 和 double包裝類(wrapper class)為 Float 和 Double)以及任意精度的小數(javamathBigDecimal)在本月的 Java 理論和實踐中Brian Goetz 探討了在 Java 程序中使用非整數類型時一些常碰到的陷阱和gotcha
    雖然幾乎每種處理器和編程語言都支持浮點運算但大多數程序員很少注意它這容易理解 — 我們中大多數很少需要使用非整數類型除了科學計算和偶爾的計時測試或基准測試程序其它情況下幾乎都用不著它同樣大多數開發人員也容易忽略 javamathBigDecimal 所提供的任意精度的小數 — 大多數應用程序不使用它們然而在以整數為主的程序中有時確實會出人意料地需要表示非整型數據例如JDBC 使用 BigDecimal 作為 SQL DECIMAL 列的首選互換格式
  IEEE 浮點
   Java 語言支持兩種基本的浮點類型float 和 double以及與它們對應的包裝類 Float 和 Double它們都依據 IEEE 標准該標准為 位浮點和 位雙精度浮點二進制小數定義了二進制標准
    IEEE 用科學記數法以底數為 的小數來表示浮點數IEEE 浮點數用 位表示數字的符號 位來表示指數 位來表示尾數即小數部分作為有符號整數的指數可以有正負之分小數部分用二進制(底數 )小數來表示這意味著最高位對應著值 ?()第二位對應著 ?()依此類推對於雙精度浮點數 位表示指數 位表示尾數IEEE 浮點值的格式如圖 所示
     IEEE 浮點數的格式
    因為用科學記數法可以有多種方式來表示給定數字所以要規范化浮點數以便用底數為 並且小數點左邊為 的小數來表示按照需要調節指數就可以得到所需的數字所以例如 可以表示為尾數為 指數為
    除了編碼所允許的值的標准范圍(對於 floatee+還有一些表示無窮大負無窮大 和 NaN(它代表不是一個數字)的特殊值這些值的存在是為了在出現錯誤條件(譬如算術溢出給負數開平方根除以 等)下可以用浮點值集合中的數字來表示所產生的結果  這些特殊的數字有一些不尋常的特征例如 是不同值但在比較它們是否相等時被認為是相等的用一個非零數去除以無窮大的數結果等於 特殊數字 NaN 是無序的使用 ==< 和 > 運算符將 NaN 與其它浮點值比較時結果為 false如果 f 為 NaN則即使 (f == f) 也會得到 false如果想將浮點值與 NaN 進行比較則使用 FloatisNaN() 方法 顯示了無窮大和 NaN 的一些屬性
    表 特殊浮點值的屬性
    表達式       結果
    Mathsqrt() > NaN
     /      > NaN
     /     > 無窮大
     /    > 負無窮大
    NaN +     > NaN
    無窮大 +    > 無窮大
    無窮大 + 無窮大  > 無窮大
    NaN >     > false
    NaN ==    > false
    NaN <     > false
    NaN == NaN    > false
     ==   > true
    基本浮點類型和包裝類浮點有不同的比較行為
  使事情更糟的是在基本 float 類型和包裝類 Float 之間用於比較 NaN 和 的規則是不同的對於 float 值比較兩個 NaN 值是否相等將會得到 false而使用 Floatequals() 來比較兩個 NaN Float 對象會得到 true造成這種現象的原因是如果不這樣的話就不可能將 NaN Float 對象用作 HashMap 中的鍵類似的雖然 在表示為浮點值時被認為是相等的但使用 pareTo() 來比較作為 Float 對象的 會顯示 小於
    浮點中的危險
    由於無窮大NaN 和 的特殊行為當應用浮點數時可能看似無害的轉換和優化實際上是不正確的例如雖然好象 f 很明顯等於 f但當 f 為 這是不正確的還有其它類似的 gotcha 顯示了其中一些 gotcha
    表 無效的浮點假定
    這個表達式……   不一定等於……     當……
     f     f         f 為
    f < g      ! (f >= g)     f 或 g 為 NaN
    f == f     true         f 為 NaN
    f + g g     f         g 為無窮大或 NaN
    捨入誤差
    浮點運算很少是精確的雖然一些數字(譬如 )可以精確地表示為二進制(底數 )小數(因為 等於 但其它一些數字(譬如 )就不能精確的表示因此浮點運算可能導致捨入誤差產生的結果接近 — 但不等於 — 您可能希望的結果例如下面這個簡單的計算將得到 而不是
  double s=;
  for (int i=; i<; i++)
  s += ;
  Systemoutprintln(s);
    類似的* 相乘所產生的結果不等於 自身加 次所得到的結果當將浮點數強制轉換成整數時產生的捨入誤差甚至更嚴重因為強制轉換成整數類型會捨棄非整數部分甚至對於那些看上去似乎應該得到整數值的計算也存在此類問題例如下面這些語句
  double d = * ;
  Systemoutprintln(d);
  Systemoutprintln((int) (d * ));
    將得到以下輸出
  
  
    這可能不是您起初所期望的
    浮點數比較指南
    由於存在 NaN 的不尋常比較行為和在幾乎所有浮點計算中都不可避免地會出現捨入誤差解釋浮點值的比較運算符的結果比較麻煩
    最好完全避免使用浮點數比較當然這並不總是可能的但您應該意識到要限制浮點數比較如果必須比較浮點數來看它們是否相等則應該將它們差的絕對值同一些預先選定的小正數進行比較這樣您所做的就是測試它們是否足夠接近(如果不知道基本的計算范圍可以使用測試abs(a/b ) < epsilon這種方法比簡單地比較兩者之差要更准確)甚至測試看一個值是比零大還是比零小也存在危險 —以為會生成比零略大值的計算事實上可能由於積累的捨入誤差會生成略微比零小的數字
    NaN 的無序性質使得在比較浮點數時更容易發生錯誤當比較浮點數時圍繞無窮大和 NaN 問題一種避免 gotcha 的經驗法則是顯式地測試值的有效性而不是試圖排除無效值在清單 有兩個可能的用於特性的 setter 的實現該特性只能接受非負數值第一個實現會接受 NaN第二個不會第二種形式比較好因為它顯式地檢測了您認為有效的值的范圍
    清單 需要非負浮點值的較好辦法和較差辦法
  // Trying to test by exclusion this doesnt catch NaN or infinity
  public void setFoo(float foo) {
  if (foo < )
  throw new IllegalArgumentException(FloattoString(f));
  thisfoo = foo;
  }
  // Testing by inclusion this does catch NaN
  public void setFoo(float foo) {
  if (foo >= && foo < FloatINFINITY)
  thisfoo = foo;
  else
  throw new IllegalArgumentException(FloattoString(f));
  }
  
    不要用浮點值表示精確值
  
    一些非整數值(如幾美元和幾美分這樣的小數)需要很精確浮點數不是精確值所以使用它們會導致捨入誤差因此使用浮點數來試圖表示象貨幣量這樣的精確數量不是一個好的想法使用浮點數來進行美元和美分計算會得到災難性的後果浮點數最好用來表示象測量值這類數值這類值從一開始就不怎麼精確
  
    用於較小數的 BigDecimal
    從 JDK Java 開發人員就有了另一種數值表示法來表示非整數BigDecimalBigDecimal 是標准的類在編譯器中不需要特殊支持它可以表示任意精度的小數並對它們進行計算在內部可以用任意精度任何范圍的值和一個換算因子來表示 BigDecimal換算因子表示左移小數點多少位從而得到所期望范圍內的值因此用 BigDecimal 表示的數的形式為
               scale
      unscaledValue* 
    用於加乘和除的方法給 BigDecimal 值提供了算術運算由於 BigDecimal 對象是不可變的這些方法中的每一個都會產生新的 BigDecimal 對象因此因為創建對象的開銷BigDecimal 不適合於大量的數學計算但設計它的目的是用來精確地表示小數如果您正在尋找一種能精確表示如貨幣量這樣的數值則 BigDecimal 可以很好地勝任該任務
    所有的 equals 方法都不能真正測試相等
    如浮點類型一樣BigDecimal 也有一些令人奇怪的行為尤其在使用 equals() 方法來檢測數值之間是否相等時要小心equals() 方法認為兩個表示同一個數但換算值不同(例如)的 BigDecimal 值是不相等的然而compareTo() 方法會認為這兩個數是相等的所以在從數值上比較兩個 BigDecimal 值時應該使用 compareTo() 而不是 equals()
    另外還有一些情形任意精度的小數運
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19617.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.