1. 程式人生 > >float精度誤差

float精度誤差

一、表示範圍

float型別資料佔4位元組記憶體(4B),共32位(32bit),在計算機中按IEEE格式儲存:1bit(符號位),8bit(指數位),23bit(尾數位)。
所以,float的指數範圍為-127~+128。其中負指數決定浮點數所能表達的絕對值最小的非零數;而正指數決定浮點數所能表達的絕對值的最大的數,也決定浮點數的取值範圍。
float的範圍:-2^128~2^128,即-3.4E+38 和 3.4E+38 之間的範圍。


二、精確度

float的精度是由尾數的位數決定的。浮點數在記憶體中是按科學計數法來儲存的,其整數部分始終隱藏著一個“1”,由於它是不變的,故不能對精度造成影響。
float:2^23=8388608,一共七位,所以一共最多七位有效數字,但絕對能保證的為6位。故:float的精度為6~7位有效數字。


 

浮點數123456789<2^128,但是打印出來為什麼是123456792?

123456789(十進位制)——>110 0101 1011 1100 1101 0001 0101(二進位制) ——>1.10 0101 1011 1100 1101 0001 0101*2^26(也就是尾數為26位)

前面說過float尾數部分可以表示的精度為23bit

1.10 0101 1011 1100 1101 0001 0101*2^26
後四位為:0101

省去後三位需要進1位 為:1000

(這裡的“進位”是猜測,具體float在記憶體中的儲存方式太過複雜,這裡的進位說,有助於理解(自圓其說),不然123456789無法轉為123456792。)

1.10 0101 1011 1100 1101 0001 1000*2^26

1.10 0101 1011 1100 1101 0001 1*2^23——>123456792(十進位制)

 

 

float和double做四則運算誤差

public class Test{
    public static void main(String args[]){
        System.out.println(0.05+0.01);
        System.out.println(1.0-0.42);
        System.out.println(4.015*100);
        System.out.println(123.3/100);
    }
}

結果:
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

原因:

那麼為什麼會出現精度丟失呢?以下僅供參考。

首先得從計算機本身去討論這個問題。我們知道,計算機並不能識別除了二進位制資料以外的任何資料。無論我們使用何種程式語言,在何種編譯環境下工作,都要先 把源程式翻譯成二進位制的機器碼後才能被計算機識別。

以上面提到的情況為例,我們源程式裡的2.4是十進位制的,計算機不能直接識別,要先編譯成二進位制。但問 題來了,2.4的二進位制表示並非是精確的2.4,反而最為接近的二進位制表示是2.3999999999999999。原因在於浮點數由兩部分組成:指數和尾數,這點如果知道怎樣進行浮點數的二進位制與十進位制轉換,應該是不難理解的。如果在這個轉換的過程中,浮點數參與了計算,那麼轉換的過程就會變得不可預 知,並且變得不可逆。我們有理由相信,就是在這個過程中,發生了精度的丟失。而至於為什麼有些浮點計算會得到準確的結果,應該也是碰巧那個計算的二進位制與 十進位制之間能夠準確轉換。

而當輸出單個浮點型資料的時候,可以正確輸出,如

double d = 2.4;
System.out.println(d);

輸出的是2.4,而不是2.3999999999999999。也就是說,不進行浮點計算的時候,在十進位制裡浮點數能正確顯示。這更印證了我以上的想法,即如果浮點數參與了計算,那麼浮點數二進位制與十進位制間的轉換過程就會變得不可預知,並且變得不可逆。

    事實上,浮點數並不適合用於精確計算,而適合進行科學計算。這裡有一個小知識:既然float和double型用來表示帶有小數點的數,那為什麼我們不稱 它們為“小數”或者“實數”,要叫浮點數呢?因為這些數都以科學計數法的形式儲存。當一個數如50.534,轉換成科學計數法的形式為5.053e1,它 的小數點移動到了一個新的位置(即浮動了)。可見,浮點數本來就是用於科學計算的,用來進行精確計算實在太不合適了。

解決:

一:設定精度!(如a<0.00001,則a=0)

二:修改資料型別為double

 

浮點數雖然表示範圍大,但是其範圍並不是連續的。

這句話的意思是,比如說1、2、3可以表示1-3的範圍,但是如果我每隔一段距離表示一個數的話,那我表示的範圍就會迅速擴大,比如我每隔兩位表示一個數,那麼儘管我依舊只可以表示3個數,但我表示的數為1、3、5,相對於表示的範圍擴大到了1-5.

而且這個每兩個數所相隔的距離不是平均相等的,往往數越大間隔也越來越大。

 

因為精度的問題,我們往往不用float a==float b或float a==0進行判斷!

解決辦法:

1.float和double只能用來做科學計算或者是工程計算,在商業計算中我們要用java.math.BigDecimal。使用BigDecimal並且一定要用String來夠造。

2.先化為整數再比較。比如23.56與23.49比較,我們化為整數,比較2356與2349大小。