Java double型別的探討

看到這篇文章實在是很興奮!!…
放在這邊與大家共享!!
 
假設你現在正在設計某些 Java 程式,其中牽涉到浮點數的計算,假定你的程式碼中設有變數其值等於0.1,然後連加它自己兩次,範例如下:
public class Fp1 {
     public static void main(String argv[]) {
          double val1 = 0.1;
          double val2 = 0.3;
 
          double d = val1 + val1 + val1;
 
          if (d != val2) {
               System.ou.println(" d != val2 ");
          }     
         
          System.out.println
          ( "d – val2 =" + (d – val2) );
     }
}
每個人都知道
0.1 + 0.1 + 0.1 = 0.3
至少在數學上是這樣的, 但是當你執行 Fp1 這個程式時,你會發現結果不再是如此,程式執行結果如下:
         d != val2
         d – val2 = 5.551115123125783E-17
這個結果似乎是說 0.1 + 0.1 + 0.1 其結果跟 0.3 差了大約 5.55e-17
問題出在哪呢?這個問題就是 IEEE754 浮點數運算( IEEE 754 floating-point arithmetic)
,這可在 Java 語言中找到, Java 語言並不使用小數點(或十進位)去表現數字,相反的它採用(二進位)分數和指數,要實際了解以上所說是什麼意思,我們考慮試著寫出數個小數以分數表示的式子
0.5 = 1/2
0.75 = 1/2 + 1/4
0.875 = 1/2 + 1/4 + 1/8
0.1 = 1/16 + 1/32 + 1/256 + 1/512 +1/4096 + 1/8192 + …
最後這一個級數是無窮止境的繼續下去,這意味著 0.1 不能準確的以浮點數型態表現,這無窮級數必須在某一點切斷, 做一些捨去或進位的動作…等等, 目的要以64位元double型態表現,而這將會導致一些誤差
底下是另外一個例子,以浮點數運算的結果將會與你預期以數學規則作運算的結果有所不同
public class Fp2 {
     public static void main(String argv[]) {
       double val1 = 12345.0;
       double val2 = 1e-16;
        
       if(val1 + val2 == val1) {
            System.out.println(" val1 + val2 == val1 ");
       }
     }
}
其結果如下:
val1 + val2 ==val1
就數學觀點來看, 如果
a > 0 && b > 0

a + b !=a
但是程式跑出來的結果明顯違反這個規則, 這問題出在浮點數型態變數的精確度,所謂的精確度就是用多少位元( bits )去表現這一個值(二進位的分數),而且其範圍與可用浮點數型態表示的值之範圍有所不同,對於Java double 形態的變數來說,有53bits的精確度,或是大約16位數(十進位),上述的例子包含一個精確度大約20位數,因為上式可改寫成
1.2345e+4 + 1e-16
因為double形態變數的精確度不足以表示這個加法的結果,所以 1e-16實際上被省略掉了
下面另一個例子是描述數學規則是如何被破壞的
public class Fp3 {
     public static void main(String argv[]) {
          double d1 = 1.6e308;
          double d2 = d1 * 2.0 / 2.0;
 
          System.out.println( d1 );
          System.out.println( d2 );
     }
}
如果d1乘以2.0且除以2.0其結果應為d1,是吧?很不幸的結果並不是如此,其結果實際如下:
        1.6e308
        Infinity
首先d1乘以2.0其值已超過精確度所能提供的位數,而之後再除以2.0已於事無補, double 型態變數其最大值大約為1.8e308, 1.6e308乘以2.0, 其結果等於3.2e308, 顯示出來的結果及為正無限大(positive infinity)
再來看看其他例子,如果你曾做過數學整數運算就會知道任何數除以零均是無意義的( ArithmeticException ),但是同樣的運算若以浮點數做處理又會如何呢?例子如下:
public class Fp4 {
     public static void main(String argv[]) {
         System.out.println( 1.0/0.0 );
         System.out.println( 1.0/-0.0 );
         System.out.println( 0.0/0.0 );
     }
}
其結果為
         Infinity
        -Infinity
         NaN
這個程式對於以浮點數除以零這個式子沒有丟出例外(Exception),當一個正數或負數除以零時,其結果均為無限大
上面的例子表示出事實上不只有一個零,其實有正負零之分,當決定除式結果是正無限大或負無限大時,零之正負將會被考慮進計算之中
那麼第三個式子又如何呢? 0.0/0.0 其結果為 NaN(not a number)
正負零與NaN有一些奇怪的特性,來看看以下的例子來了解這句話
public class Fp5 {
     public static void main(String argv[]) {
          double d1 = -0.0;
          double d2 = +0.0;
          if (d1<d2) {
             System.out.println( -0.0 < +0.0);
          }
          double d3 = 0.0/0.0;
          if(d3 == d3)
          { System.out.println( " d3 == d3 " ); }
     }
}
如果你執行 Fp5 這個程式,你將會看不到任何東西,如同你所看到之前的例子,零的正負號會影響計算, 就像 1.0/ -0.0 == -infinity 和 1.0/ +0.0 == infinity 但這並不能就因此說 -0.0 < +0.0,上述的例子說明了這件事( d1不小於d2,所以並不顯示任何東西), 不管就你的觀點 -0.0是否跑得比 +0.0還"前面"
Fp5 也說出了一個重點那就是 NaN 是不受控制的( unordered ), 所以d3並不等於它自己,這是一個方法(way)你可以寫出一個方法(method)去分辨一個數是否是NaN(not a number),如果一個數不等於它自己那麼它就是NaN,這種方法(method)事實上已經存在,你可以看看 Float.isNan和Double.isNaN這兩個方法
我們來看看最後這個例子,我們用這個例子來試著描述一個規則關於之前所討論到奇特的浮點數
public calss Fp6 {
       static double[] list =
       {
            Double.NEGATIVE_INFINITY,
            -0.0,
            +0.0,
            Double.MIN_VALUE,
            Double.MAX_VALUE,
            Double.POSITIVE_INFINITY
        };
   
        public static void main(String argv[]) {
            for (int i = 0; i < list.length; i++)
            {
                System.out.println(list[i]);
            }
        }
}
其結果為
            -Infinity
            -0.0
             0.0
             4.9E-324
             1.7976931348623157E308
             Infinity
注意 Double.MAX_VALUE 和 Double.POSITIVE_INFINITY 是不同的值
這篇文章提出有關浮點數運算的一些特色.
在使用浮點數時必須要小心,因為它所產生出來的結果將會與你所預期的不同,如果你僅僅是應用數學法則去使用它的話.
 
事實上, 可以利用 java.math.BigDecimal 來考慮解決這個問題!!
一個範例程式如下:
import java.math.BigDecimal;
public class demo {
    public static void main (String [] args){
       // Double.SIZE = 100;
        double d = 1d/3d;
        System.out.println(d);
        BigDecimal bd1 = new BigDecimal(1);
        BigDecimal bd2 = new BigDecimal(3);
        System.out.println(bd1.divide(bd2,200,BigDecimal.ROUND_HALF_UP).toString());
    }
}
 
廣告

About fenjj

Perfect !!??...
本篇發表於 Uncategorized。將永久鏈結加入書籤。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s