java 將小數拆分為兩部分+浮點型精度丟失問題


問題:將一個String類型的小數拆分為整數部分和小數部分,如9.9拆分為9和0.9

1.將小數的整數和小數部分拆分開

public float numberSub(String totalMoney){
        float moneyFloat=Float.parseFloat(totalMoney);
        System.out.println("moneyFloat="+moneyFloat);
        int moneyInteger=(int) moneyFloat;
        System.out.println("moneyInteger="+moneyInteger);
        float moneyDecimal=moneyFloat-moneyInteger;
        System.out.println("moneyDecimal="+moneyDecimal);
        float result=this.sub(moneyFloat, moneyInteger);
        return result;
    }

上面這個方法里面,float-->int轉化時直接丟棄小數部分,從而取得小數中的整數,而后作差得到小數部分,但是看下面輸出:

2.浮點型表示一個小數的時候存在精度不准確的問題

 原因:

首先我們要搞清楚下面兩個問題:  
   (1) 十進制整數如何轉化為二進制數  
         算法很簡單。舉個例子,11表示成二進制數:  
                    11/2=5 1  
  
                     5/2=21  
  
                     2/2=10  
  
                     1/2=01  
  
                      0結束         11二進制表示為(從下往上):1011  
  
        這里提一點:只要遇到除以后的結果為0了就結束了,大家想一想,所有的整數除以2是不是一定能夠最終得到0。換句話說,所有的整數轉變為二進制數的算法會不會無限循環下去呢?絕對不會,整數永遠可以用二進制精確表示 ,但小數就不一定了。  
  
    (2) 十進制小數如何轉化為二進制數  
         算法是乘以2直到沒有了小數為止。舉個例子,0.9表示成二進制數  
                   0.9*2=1.8   取整數部分 1  
  
                   0.8(1.8的小數部分)*2=1.6    取整數部分 1  
  
                   0.6*2=1.2   取整數部分 1  
  
                   0.2*2=0.4   取整數部分 0  
  
                   0.4*2=0.8   取整數部分 0  
  
                   0.8*2=1.6 取整數部分 1  
  
                   0.6*2=1.2   取整數部分 0  
  
                            .........      0.9二進制表示為(從上往下): 1100100100100......  
  
         注意:上面的計算過程循環了,也就是說*2永遠不可能消滅小數部分,這樣算法將無限下去。很顯然,小數的二進制表示有時是不可能精確的 。其實道理很簡單,十進制系統中能不能准確表示出1/3呢?同樣二進制系統也無法准確表示1/10。這也就解釋了為什么浮點型減法出現了精度丟失的問題。

3.驗證

眾所周知、 Java 的float型在內存中占4個字節。float的32個二進制位結構如下

float內存存儲結構   

 4bytes          31                  30                29----23          22----0         

 表示       實數符號位      指數符號位        指數位          有效數位

 其中符號位1表示正,0表示負。有效位數位24位,其中一位是實數符號位。

將一個float型轉化為內存存儲格式的步驟為:

     (1)先將這個實數的絕對值化為二進制格式,注意實數的整數部分和小數部分的二進制方法在上面已經探討過了。 
     (2)將這個二進制格式實數的小數點左移或右移n位,直到小數點移動到第一個有效數字的右邊。 
     (3)從小數點右邊第一位開始數出二十三位數字放入第22到第0位。 
     (4)如果實數是正的,則在第31位放入“0”,否則放入“1”。 
     (5)如果n 是左移得到的,說明指數是正的,第30位放入“1”。如果n是右移得到的或n=0,則第30位放入“0”。 
     (6)如果n是左移得到的,則將n減去1后化為二進制,並在左邊加“0”補足七位,放入第29到第23位。如果n是右移得到的或n=0,則將n化為二進制后在左邊加“0”補足七位,再各位求反,再放入第29到第23位。

我們以數字8舉例驗證,float類型為8.0

1.將8.0轉換為二進制之后是1000.0

2.將小數點左移三位到第一個有效位右側1.0000(保證有效位數24位)得1.00000000000000000000000(只保留24位,多出的被截取掉了,從而引起了誤差!

3.這時已經有了二十四位有效數字,將最左邊一位“1”去掉,得到0000000000000000000000共23位,將它放入float存儲結構的第22到第0位。

4.因為8.0是正數,因此在第31位實數符號位放入“0”。

5.由於我們把小數點左移,因此在第30位指數符號位放入“1”。

6.因為我們是把小數點左移3位,因此將3減去1得2,化為二進制並補足7位得到0000010,放入第29到第23位。

最后得到0 1 0000010 0000000000000000000000

代碼中打印出來如下:

public void testJavaDataType(){
        int aint=8;
        float afloat=8;
        int fl=Float.floatToIntBits(afloat);
        double adouble=8;
        long dl=Double.doubleToLongBits(adouble);
        System.out.println("Int 8 :"+Integer.toBinaryString(aint));
        System.out.println("Float 8 :"+Integer.toBinaryString(fl));
        System.out.println("Double 8 :"+Long.toBinaryString(dl));
    }

打印的時候如果是整數則第31位0默認不打印,籃框是第30位指數符號位,紅框是指數位,藍底是有效位數

很顯然對於小數來說一個有限的23位有效位數是不足以精確表示一個小數的。

4.BigDecimal解決

public float sub(float a,float b){
        BigDecimal bda=new BigDecimal(Float.toString(a));
        BigDecimal bdb=new BigDecimal(String.valueOf(b));
        return bda.subtract(bdb).floatValue();
    }

 5.利用String截取小數點來解決該問題

public float stringSub(String totalMoeny){
        String[] strArray=totalMoeny.split("\\.");
        if(strArray.length>=2){
            float a=Float.valueOf(strArray[0]);
            System.out.println(a);
            float b=Float.parseFloat("0."+strArray[1]);
            System.out.println(b);
            return a-b;
        }else{
            return 0l;    
        }
    }

 

參考文章:

http://blog.csdn.net/chencheng19912012/article/details/30072389

http://www.cnblogs.com/jym-sunshine/p/5853070.html

http://blog.csdn.net/tomcat_2014/article/details/51453988


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM