1. 程式人生 > >Float 記憶體儲存詳解

Float 記憶體儲存詳解


轉自http://www.cnblogs.com/yewsky/articles/1864934.html和http://blog.csdn.net/soft200816/article/details/7569722

為何浮點數可能丟失精度浮點十進位制值通常沒有完全相同的二進位制表示形式。 這是 CPU 所採用的浮點資料表示形式的副作用。 為此,可能會經歷一些精度丟失,並且一些浮點運算可能會產生意外的結果。

導致此行為的原因是下面之一:

十進位制數的二進位制表示形式可能不精確。

使用的數字之間型別不匹配(例如,混合使用浮點型和雙精度型)。

為解決此行為,大多數程式設計師或是確保值比需要的大或者小,或是獲取並使用可以維護精度的二進位制編碼的十進位制 (BCD) 庫。

現在我們就詳細剖析一下浮點型運算為什麼會造成精度丟失?

1、小數的二進位制表示問題

       首先我們要搞清楚下面兩個問題:

     (1) 十進位制整數如何轉化為二進位制數

           演算法很簡單。舉個例子,11表示成二進位制數:

                     11/2=5 餘   1

                       5/2=2   餘   1

                       2/2=1   餘   0

                       1/2=0   餘   1

                          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。這也就解釋了為什麼浮點型減法出現了"減不盡"的精度丟失問題。

 

2、 float型在記憶體中的儲存

     眾所周知、 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位。

 

          舉例說明: 11.9的記憶體儲存格式

       (1) 將11.9化為二進位制後大約是" 1011. 1110011001100110011001100..."。

       (2) 將小數點左移三位到第一個有效位右側: "1. 011 11100110011001100110 "。 保證有效位數24位,右側多餘的擷取(誤差在這裡產生了 )。

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

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

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

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

 

           最後表示11.9為: 0 1 0000010 011 11100110011001100110

 

           再舉一個例子:0.2356的記憶體儲存格式
      (1)將0.2356化為二進位制後大約是0.00111100010100000100100000。
      (2)將小數點右移三位得到1.11100010100000100100000。
      (3)從小數點右邊數出二十三位有效數字,即11100010100000100100000放
入第22到第0位。
      (4)由於0.2356是正的,所以在第31位放入“0”。
      (5)由於我們把小數點右移了,所以在第30位放入“0”。
      (6)因為小數點被右移了3位,所以將3化為二進位制,在左邊補“0”補足七
位,得到0000011,各位取反,得到1111100,放入第29到第23位。
       

           最後表示0.2356為:0 0 1111100 11100010100000100100000

 

           將一個記憶體儲存的float二進位制格式轉化為十進位制的步驟:
     (1)將第22位到第0位的二進位制數寫出來,在最左邊補一位“1”,得到二十四位有效數字。將小數點點在最左邊那個“1”的右邊。
     (2)取出第29到第23位所表示的值n。當30位是“0”時將n各位求反。當30位是“1”時將n增1。
     (3)將小數點左移n位(當30位是“0”時)或右移n位(當30位是“1”時),得到一個二進位制表示的實數。
     (4)將這個二進位制實數化為十進位制,並根據第31位是“0”還是“1”加上正號或負號即可。

 

3、浮點型的減法運算

 

          浮點加減運算過程比定點運算過程複雜。完成浮點加減運算的操作過程大體分為四步:
  (1) 0運算元的檢查;

                如果判斷兩個需要加減的浮點數有一個為0,即可得知運算結果而沒有必要再進行有序的一些列操作。

   (2) 比較階碼(指數位)大小並完成對階;

                兩浮點數進行加減,首先要看兩數的 指數位 是否相同,即小數點位置是否對齊。若兩數 指數位 相同,表示小數點是對齊的,就可以進行尾數的加減運算。反之,若兩數階碼不同,表示小數點位置沒有對齊,此時必須使兩數的階碼相同,這個過程叫做對階

                如何對 階(假設兩浮點數的指數位為 Ex 和 Ey ):

        通過尾數的移位以改變 Ex 或 Ey ,使之相等。 由於浮點表示的數多是規格化的,尾數左移會引起最高有位的丟失,造成很大誤差;而尾數右移雖引起最低有效位的丟失,但造成的誤差較小,因此,對階操作規定使尾數右移,尾數右移後使階碼作相應增加,其數值保持不變。很顯然,一個增加後的階碼與另一個相等,所增加的階碼一定是小階。因此在對階時,總是使小階向大階看齊 ,即小階的尾數向右移位 ( 相當於小數點左移 ) ,每右移一位,其階碼加 1 ,直到兩數的階碼相等為止,右移的位數等於階差 △ E
   (3) 尾數(有效數位)進行加或減運算;

               對階完畢後就可 有效數位 求和。 不論是加法運算還是減法運算,都按加法進行操作,其方法與定點加減運算完全一樣。
   (4) 結果規格化並進行舍入處理。

               

4、 計算12.0f-11.9f

       12.0f 的記憶體儲存格式為: 0 1 0000010 10000000000000000000000    

     11.9f 的記憶體儲存格式為:   0 1 0000010 011 11100110011001100110


     可見兩數的指數位完全相同,只要對有效數位進行減法即可。

     12.0f-11.9f   結果:         0 1 0000010 00000011001100110011010

 

     將結果還原為十進位制為: 0.000 11001100110011010= 0.10000038

詳細的分析

由於對float或double 的使用不當,可能會出現精度丟失的問題。問題大概情況可以通過如下程式碼理解:

view plaincopy to clipboardprint?
public class FloatDoubleTest {  
public static void main(String[] args) {  
float f = 20014999;  
double d = f;  
double d2 = 20014999;  
System.out.println("f=" + f);  
System.out.println("d=" + d);  
System.out.println("d2=" + d2);  
}  
}
public class FloatDoubleTest {
public static void main(String[] args) {
float f = 20014999;
double d = f;
double d2 = 20014999;
System.out.println("f=" + f);
System.out.println("d=" + d);
System.out.println("d2=" + d2);
}
}

得到的結果如下:

f=2.0015E7

d=2.0015E7

d2=2.0014999E7

從輸出結果可以看出double 可以正確的表示20014999 ,而float 沒有辦法表示20014999 ,得到的只是一個近似值。這樣的結果很讓人訝異。20014999 這麼小的數字在float下沒辦法表示。於是帶著這個問題,做了一次關於float和double學習,做個簡單分享,希望有助於大家對java 浮點數的理解。

 

關於 java 的 float 和 double

Java 語言支援兩種基本的浮點型別: float 和 double 。java 的浮點型別都依據 IEEE 754 標準。IEEE 754 定義了32 位和 64 位雙精度兩種浮點二進位制小數標準。

IEEE 754 用科學記數法以底數為 2 的小數來表示浮點數。32 位浮點數用 1 位表示數字的符號,用 8 位來表示指數,用 23 位來表示尾數,即小數部分。作為有符號整數的指數可以有正負之分。小數部分用二進位制(底數 2 )小數來表示。對於64 位雙精度浮點數,用 1 位表示數字的符號,用 11 位表示指數,52 位表示尾數。如下兩個圖來表示:

float(32位):

 

 

 

double(64位):


 

 

 

都是分為三個部分:

(1) 一個單獨的符號位s 直接編碼符號s 。

(2)k 位的冪指數E ,移碼錶示 。

(3)n 位的小數,原碼錶示 。

那麼 20014999 為什麼用 float 沒有辦法正確表示?

結合float和double的表示方法,通過分析 20014999 的二進位制表示就可以知道答案了。

以下程式可以得出 20014999 在 double 和 float 下的二進位制表示方式。

view plaincopy to clipboardprint?
public class FloatDoubleTest3 {  
public static void main(String[] args) {  
double d = 8;  
long l = Double.doubleToLongBits(d);  
System.out.println(Long.toBinaryString(l));  
float f = 8;  
int i = Float.floatToIntBits(f);  
System.out.println(Integer.toBinaryString(i));  
}  
}
public class FloatDoubleTest3 {
public static void main(String[] args) {
double d = 8;
long l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));
float f = 8;
int i = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(i));
}
}

輸出結果如下:

Double:100000101110011000101100111100101110000000000000000000000000000

Float:1001011100110001011001111001100

對於輸出結果分析如下。對於都不 double 的二進位制左邊補上符號位 0 剛好可以得到 64 位的二進位制數。根據double的表示法,分為符號數、冪指數和尾數三個部分如下:

0 10000010111 0011000101100111100101110000000000000000000000000000

對於 float 左邊補上符號位 0 剛好可以得到 32 位的二進位制數。 根據float的表示法, 也分為 符號數、冪指數和尾數三個部分如下 :

0 10010111 00110001011001111001100

綠色部分是符號位,紅色部分是冪指數,藍色部分是尾數。

對比可以得出:符號位都是 0 ,冪指數為移碼錶示,兩者剛好也相等。唯一不同的是尾數。

在 double 的尾數為: 001100010110011110010111 0000000000000000000000000000 ,省略後面的零,至少需要24位才能正確表示 。

而在 float 下面尾數為: 00110001011001111001100 ,共 23 位。

為什麼會這樣?原因很明顯,因為 float尾數 最多隻能表示 23 位,所以 24 位的 001100010110011110010111 在 float 下面經過四捨五入變成了 23 位的 00110001011001111001100 。所以 20014999 在 float 下面變成了 20015000 。

也就是說 20014999 雖然是在float的表示範圍之內,但 在 IEEE 754 的 float 表示法精度長度沒有辦法表示出 20014999 ,而只能通過四捨五入得到一個近似值。




Float 記憶體儲存詳解

標籤: float儲存javastringintegerdiv 2277人閱讀 評論(0) 收藏 舉報 定點數表達法的缺點在於其形式過於僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利於同時表達特別大的數或者特別小的數。
計算機系統採納了所謂的浮點數表達方式。這種表達方式利用科學計數法來表達實數,即用一個尾數(Mantissa也叫有效數字 ),一個基數(Base),一個指數(Exponent)以及

一個表示正負的符號來表達實數。浮點數利用指數達到了浮動小數點的效果,從而可以靈活地表達更大範圍的實數。


當一個浮點數的尾數為0,不論其階碼為何值,該浮點數的值都為0。當階碼的值為它能表示的最小一個值或更小的值時,不管其尾數為何值,計算機都把該浮點數看成零值,通常稱

其為機器零,此時該浮點數的所有各位(包括階碼位和尾數位)都清為0值。

Java 平臺上的浮點數型別 float 和 double 採納了 IEEE 754 標準中所定義的單精度 32 位浮點數和雙精度 64 位浮點數的格式。

在 IEEE 標準中,浮點數是將特定長度的連續位元組的所有二進位制位分割為特定寬度的符號域,指數域和尾數域三個域,其中儲存的值分別用於表示給定二進位制浮點數中的符號,指

數和尾數。這樣,通過尾數和可以調節的指數就可以表達給定的數值了。具體的格式參見下面的圖例:




在上面的圖例中,第一個域為符號域。其中 0 表示數值為正數,而 1 則表示負數。

第二個域為指數域,對應於我們之前介紹的二進位制科學計數法中的指數部分。其中單精度數為 8 位,雙精度數為 11 位。以單精度數為例,8 位的指數為可以表達 0 到 255 之間

的 255 個指數值(注:指數8位的最高位都是數值位,沒有符號位)。但是,指數可以為正數,也可以為負數。為了處理負指數的情況,實際的指數值按要求需要加上一個偏差

(Bias)值作為儲存在指數域中的值,單精度數的偏差值為 127(2^7-1),而雙精度數的偏差值為1023(2^10-1)。比如,單精度的實際指數值 0 在指數域中將儲存為 127;而

儲存在指數域中的 64 則表示實際的指數值 -63(64-127=-63)。 偏差的引入使得對於單精度數,實際可以表達的指數值的範圍就變成 -127(表示小數點需向左移動127位) 到

128(表示小數點需向右移動128位) 之間(包含兩端)。我們不久還將看到,實際的指數值 -127(全 0)以及 +128(全 1)保留用作特殊值的處理。這樣,實際可以表達的有效

指數範圍就在 -127 和 127 之間。

第三個域為尾數域,其中單精度數為 23 位長,雙精度數為 52 位長。除了我們將要講到的某些特殊值外,IEEE 標準要求浮點數必須是規範的。這意味著尾數的小數點左側必須為

1,因此我們在儲存尾數的時候,可以省略小數點前面這個 1,從而騰出一個二進位制位來儲存更多的尾數。這樣我們實際上用 23 位長的尾數域表達了 24 位的尾數。比如對於單精

度數而言,二進位制的 1001.101(對應於十進位制的 9.625)可以表達為 1.001101 × 2^3,所以實際儲存在尾數域中的值為 00110100000000000000000,即去掉小數點左側的 1,並

用 0 在右側補齊。
值得注意的是,對於單精度數,由於我們只有 24 位的尾數(其中一位隱藏),所以可以表達的最大尾數為 2^24- 1 = 16,777,215。特別的,16,777,216 是偶數,所以我們可以

通過將它除以 2 並相應地調整指數來儲存這個數,這樣 16,777,216 同樣可以被精確的儲存。相反,數值 16,777,217 則無法被精確的儲存。由此,我們可以看到單精度的浮點數

可以表達的十進位制數值中,真正有效的數字不高於 8 位。事實上,對相對誤差的數值分析結果顯示有效的精度大約為 7.22 位(由於位數不可取小數,所以單精度的精度為7,即

可精確到小數點後7位)。參考下面的示例:
Java程式碼

   1. System.out.println(16777215f);//1.6777215E7 
   2. System.out.println(16777216f);//1.6777216E7 
   3. System.out.println(16777217f);//1.6777216E7 
   4. System.out.println(16777218f);//1.6777218E7 
   5. System.out.println(16777219f);//1.677722E7 
   6. System.out.println(16777220f);//1.677722E7 
   7. System.out.println(16777221f);//1.677722E7 
   8. System.out.println(16777222f);//1.6777222E7 
   9. System.out.println(16777223f);//1.6777224E7 
  10. System.out.println(16777224f);//1.6777224E7 
  11. System.out.println(16777225f);//1.6777224E7 

  System.out.println(16777215f);//1.6777215E7
  System.out.println(16777216f);//1.6777216E7
  System.out.println(16777217f);//1.6777216E7
  System.out.println(16777218f);//1.6777218E7
  System.out.println(16777219f);//1.677722E7
  System.out.println(16777220f);//1.677722E7
  System.out.println(16777221f);//1.677722E7
  System.out.println(16777222f);//1.6777222E7
  System.out.println(16777223f);//1.6777224E7
  System.out.println(16777224f);//1.6777224E7
  System.out.println(16777225f);//1.6777224E7

請看結果推導分析:

111111111111111111111111          16777215f
1.11111111111111111111111          剛好是23位,不會丟失精度,能精確表示
0 23+127 11111111111111111111111
0 10010110 11111111111111111111111



1000000000000000000000000         16777216f
1.00000000000000000000000 0        去掉的是0,所以還是能準確表示
0 24+127 00000000000000000000000
0 10010111 00000000000000000000000



1000000000000000000000001          16777217f
1.00000000000000000000000 1        不能準確表示。先試著進位
1.00000000000000000000001          由於進位後,結果的最末們不是0,所以直接舍掉
1.00000000000000000000000          到這裡結果就是16777216f



1000000000000000000000010         16777218f
1.0000000000000000000001 0         去掉的是0,所以還是能準確表示
0 24+127 00000000000000000000001
0 10010111 00000000000000000000001



1000000000000000000000011         16777219f
1.0000000000000000000001 1         不能準確表示。先試著進位
1.0000000000000000000010           進位後的結果,最末們為0,所以進位成功
0 24+127 00000000000000000000010
0 10010111 00000000000000000000010
1.000000000000000000000100*2^24    16777219f儲存在記憶體中的結果實質上為16777220

........



根據標準要求,無法精確儲存的值必須向最接近的可儲存的值進行舍入。這有點像我們熟悉的十進位制的四捨五入,0就舍,但1不一定就進,而是在前後兩個等距接近的可儲存的值

中,取其中最後一位有效數字為零者(即先試著進1,會得到最後結果,然後看這個結果的尾數最後位是否為0,如果是則進位,否則直接捨去)。從上面的示例中可以看出,奇數

都被舍入為偶數(舍入到偶數有助於從某些角度減小計算中產生的舍入誤差累積問題。因此為 IEEE 標準所採用),且有舍有進。我們可以將這種舍入誤差理解為"半位"的誤差。

所以,為了避免 7.22 對很多人造成的困惑,有些文章經常以 7.5來說明單精度浮點數的精度問題。


假定我們有一個 32 位的資料,用十六進位制表示為 0xC0B40000,並且我們知道它實際上是一個單精度的浮點數。為了得到該浮點數實際表達的實數,我們首先將它變換為二進位制形

式:
1100 0000 1011 0100 0000 0000 0000 0000
接著按照浮點數的格式切分為相應的域:
1 10000001 01101000000000000000000
符號域 1 意味著負數;指數域為 129 意味著實際的指數為 2 (減去偏差值 127);尾數域為 01101 意味著實際的二進位制尾數為 1.01101 (加上隱含的小數點前面的 1)。所以

,實際的實數為:
-1.01101 × 2^2 = -101.101 = -5.625
或使用Java也可計算出:
Java程式碼

   1. /*
   2. 注:Float.intBitsToFloat 方法是將記憶體中int數值的二進位制看作是float的
   3. 二進位制制,這樣很方便使用一個二進位制位來構造一個float。
   4. */ 
   5. System.out.println(Float.intBitsToFloat(0xc0B40000));//-5.625 

/*
注:Float.intBitsToFloat方法是將記憶體中int數值的二進位制看作是float的
二進位制制,這樣很方便使用一個二進位制位來構造一個float。
*/
System.out.println(Float.intBitsToFloat(0xc0B40000));//-5.625



從實數向浮點數變換稍微麻煩一點。假定我們需要將實數 -9.625 表達為單精度的浮點數格式。方法是首先將它用二進位制浮點數表達,然後變換為相應的浮點數格式。
首先,將小數點左側的整數部分變換為其二進位制形式,9 的二進位制性形式為 1001。處理小數部分的演算法是將我們的小數部分乘以基數 2,記錄乘積結果的整數部分,接著將結果的

小數部分繼續乘以 2,並不斷繼續該過程:
0.625 × 2 = 1.25   1
0.25× 2 = 0.5        0
0.5× 2 = 1            1
                            0
當最後的結果為零時,結束這個過程。這時右側的一列數字就是我們所需的二進位制小數部分,即 0.101。這樣,我們就得到了完整的二進位制形式 1001.101。用規範浮點數表達為

1.001101 × 2^3。
因為是負數,所以符號域為 1。指數為 3,所以指數域為 3 + 127 = 130,即二進位制的 10000010。尾數省略掉小數點左側的 1 之後為 001101,右側用零補齊。最終結果為:
1 10000010 00110100000000000000000
最後可以將浮點數形式表示為十六進位制的資料如下:
1100 0001 0001 1010 0000 0000 0000 0000
或使用Java也可計算出:
Java程式碼

   1. /*
   2. 注:Float.floatToIntBits:將記憶體中的float型數值的二進位制看作是對應
   3. 的int型別的二進位制,這樣很方便的將一個float的記憶體資料以二進位制來表示。
   4. 11000001000110100000000000000000
   5. */ 
   6. System.out.println(Integer.toBinaryString(Float.floatToIntBits(-9.625F))); 

/*
注:Float.floatToIntBits:將記憶體中的float型數值的二進位制看作是對應
的int型別的二進位制,這樣很方便的將一個float的記憶體資料以二進位制來表示。
11000001000110100000000000000000
*/
System.out.println(Integer.toBinaryString(Float.floatToIntBits(-9.625F)));



很簡單?等等!你可能已經注意到了,在上面這個我們有意選擇的示例中,不斷的將產生的小數部分乘以 2 的過程掩蓋了一個事實。該過程結束的標誌是小數部分乘以 2 的結果

為 1,不難想象,很多小數根本不能經過有限次這樣的過程而得到結果(比如最簡單的 0.1)。我們已經知道浮點數尾數域的位數是有限的,為此,浮點數的處理辦法是持續該過

程直到由此得到的尾數足以填滿尾數域,之後對多餘的位進行舍入。換句話說,除了我們之前講到的精度問題之外,十進位制到二進位制的變換也並不能保證總是精確的,而只能是近

似值。事實上,只有很少一部分十進位制小數具有精確的二進位制浮點數表達。再加上浮點數運算過程中的誤差累積,結果是很多我們看來非常簡單的十進位制運算在計算機上卻往往出

人意料。這就是最常見的浮點運算的"不準確"問題。參見下面的 Java 示例:
Java程式碼

   1. // 34.6-34.0=0.5999985 
   2. System.out.print("34.6-34.0=" + (34.6f-34.0f)); 

// 34.6-34.0=0.5999985
System.out.print("34.6-34.0=" + (34.6f-34.0f));

產生這個誤差的原因是 34.6 無法精確的表達為相應的浮點數,而只能儲存為經過舍入的近似值。這個近似值與 34.0 之間的運算自然無法產生精確的結果。



當指數、尾數都為0,則規定該浮點數為0。
當指數255(全1),且尾數為0時,表示無窮大,用符號位來確定是正無窮大還是負無窮大。
當指數255(全1),且尾數不為0時,表示NaN(不是一個數)。
最大的float數:0,11111110,1111111 11111111 11111111   用10進製表示約為   +3.4E38
最小的float數:1,11111110,1111111 11111111 11111111   用10進製表示約為   -3.4E38
絕對值最小的float 數:0,00000000,0000000 00000000 00000001和1,00000000,0000000   00000000 00000001


浮點數的精度:
單精度數的尾數用23位儲存,加上預設的小數點前的1位1,2^(23+1) = 16777216。因為 10^7 < 16777216 < 10^8,所以說單精度浮點數的有效位數(精度)是7位(即小數點後7

位)。
雙精度的尾數用52位儲存,2^(52+1) = 9007199254740992,10^16 < 9007199254740992 < 10^17,所以雙精度的有效位數(精度)是16位(即小數點後16位)。
如果你在浮點數的有效位後增加數字的話,結果是不會變化的:
Java程式碼

   1. System.out.println(1.67772156F);//1.6777215 
   2. System.out.println(1.67772150123456789D);//1.6777215012345679 

System.out.println(1.67772156F);//1.6777215
System.out.println(1.67772150123456789D);//1.6777215012345679



float取值範圍:
負數取值範圍為 -3.4028235E+38 ~ -1.401298E-45,正數取值範圍為 1.401298E-45 ~ 3.4028235E+38。
Float.MIN_VALUE:保持 float 型別資料的最小正非零值的常量,最小正非零值為2^-149。該常量等於十六進位制的浮點文字 0x0.000002P-126f,也等於 Float.intBitsToFloat

(0x1)=1.4e-45f。(二進位制為:0,00000000,0000000 00000000 00000001)
Float.MAX_VALUE:保持 float 型別的最大正有限大值的常量,最大正有限大值為(2-2^-23)*2^127。該常量等於十六進位制的浮點文字 0x1.fffffeP+127f,也等於

Float.intBitsToFloat(0x7f7fffff)=3.4028235e+38f。(二進位制為:0,11111110,1111111 11111111 11111111)
Java程式碼

   1. public class FloatTest { 
   2.     public static void main(String[] args) { 
   3.         // 只要指數為255,而尾數不為0時,該數不是一個數 
   4.         System.out.println(format(Integer.toBinaryString(0x7FC00000)) + " " 
   5.                 + Float.intBitsToFloat(0x7FC00000));// NaN 
   6.         System.out.println(format(Integer.toBinaryString(0xFFC00000)) + " " 
   7.                 + Float.intBitsToFloat(0xFFC00000));// NaN 
   8.         System.out.println(format(Integer.toBinaryString(0x7F800001)) + " " 
   9.                 + Float.intBitsToFloat(0x7F800001));// NaN 
  10.         // 指數為255,尾數為0時,該數就是無窮,符號位區分正無窮與負無窮 
  11.         System.out.println(format(Integer.toBinaryString(0x7F800000)) + " " 
  12.                 + Float.intBitsToFloat(0x7F800000));// Infinity 
  13.         System.out.println(format(Integer.toBinaryString(0xFF800000)) + " " 
  14.                 + Float.intBitsToFloat(0xFF800000));// Infinity 
  15.         //規定指數與尾數都為0時,規定結果就是0 
  16.         System.out.println(format(Integer.toBinaryString(0x0)) + " " 
  17.                 + Float.intBitsToFloat(0x0));        
  18.         // 正的最小float,趨近於0 
  19.         System.out.println(format(Integer.toBinaryString(0x1)) + " " 
  20.                 + Float.intBitsToFloat(0x1));// 1.4E-45 
  21.         // 正的最大float 
  22.         System.out.println(format(Integer.toBinaryString(0x7f7fffff)) + " " 
  23.                 + Float.intBitsToFloat(0x7f7fffff));// 3.4028235E38 
  24.         // 負的最大float,趨近於0 
  25.         System.out.println(format(Integer.toBinaryString(0x80000001)) + " " 
  26.                 + Float.intBitsToFloat(0x80000001));// -1.4E-45 
  27.         // 負的最小float 
  28.         System.out.println(format(Integer.toBinaryString(0xFf7fffff)) + " " 
  29.                 + Float.intBitsToFloat(0xFf7fffff));// -3.4028235E38 
  30.     } 
  31.  
  32.     private static String format(String str) { 
  33.         StringBuffer sb = new StringBuffer(str); 
  34.         int sub = 32 - str.length(); 
  35.         if (sub != 0) { 
  36.             for (int i = 0; i < sub; i++) { 
  37.                 sb.insert(0, 0); 
  38.             } 
  39.         } 
  40.         sb.insert(1, " "); 
  41.         sb.insert(10, " "); 
  42.         return sb.toString(); 
  43.     } 
  44. } 

public class FloatTest {
public static void main(String[] args) {
// 只要指數為255,而尾數不為0時,該數不是一個數
System.out.println(format(Integer.toBinaryString(0x7FC00000)) + " "
+ Float.intBitsToFloat(0x7FC00000));// NaN
System.out.println(format(Integer.toBinaryString(0xFFC00000)) + " "
+ Float.intBitsToFloat(0xFFC00000));// NaN
System.out.println(format(Integer.toBinaryString(0x7F800001)) + " "
+ Float.intBitsToFloat(0x7F800001));// NaN
// 指數為255,尾數為0時,該數就是無窮,符號位區分正無窮與負無窮
System.out.println(format(Integer.toBinaryString(0x7F800000)) + " "
+ Float.intBitsToFloat(0x7F800000));// Infinity
System.out.println(format(Integer.toBinaryString(0xFF800000)) + " "
+ Float.intBitsToFloat(0xFF800000));// Infinity
//規定指數與尾數都為0時,規定結果就是0
System.out.println(format(Integer.toBinaryString(0x0)) + " "
+ Float.intBitsToFloat(0x0));
// 正的最小float,趨近於0
System.out.println(format(Integer.toBinaryString(0x1)) + " "
+ Float.intBitsToFloat(0x1));// 1.4E-45
// 正的最大float
System.out.println(format(Integer.toBinaryString(0x7f7fffff)) + " "
+ Float.intBitsToFloat(0x7f7fffff));// 3.4028235E38
// 負的最大float,趨近於0
System.out.println(format(Integer.toBinaryString(0x80000001)) + " "
+ Float.intBitsToFloat(0x80000001));// -1.4E-45
// 負的最小float
System.out.println(format(Integer.toBinaryString(0xFf7fffff)) + " "
+ Float.intBitsToFloat(0xFf7fffff));// -3.4028235E38
}

private static String format(String str) {
StringBuffer sb = new StringBuffer(str);
int sub = 32 - str.length();
if (sub != 0) {
for (int i = 0; i < sub; i++) {
sb.insert(0, 0);
}
}
sb.insert(1, " ");
sb.insert(10, " ");
return sb.toString();
}
}


2^-149=(0.00000000000000000000001) *2^-127=(00000000000000000000000.1) *2^-149=0x0.000002P-126f=0.0000 0000 0000 0000 0000 0010*2^126
(2-2^-23)*2^127=(10.00000000000000000000000-0.00000000000000000000001) *2^127
0x1.fffffeP+127f=0x1.1111 1111 1111 1111 1111 1110P+127



double取值範圍:
負值取值範圍 -1.79769313486231570E+308 ~ -4.94065645841246544E-324,正值取值範圍為 4.94065645841246544E-324 ~ 1.79769313486231570E+308。
Double.MIN_VALUE:保持 double 型別資料的最小正非零值的常量,最小正非零值為 2^-1074。它等於十六進位制的浮點字面值 0x0.0000000000001P-1022,也等於

Double.longBitsToDouble(0x1L)。
Double.MAX_VALUE 保持 double 型別的最大正有限值的常量,最大正有限值為 (2-2^-52)*2^1023。它等於十六進位制的浮點字面值 0x1.fffffffffffffP+1023,也等於

Double.longBitsToDouble(0x7fefffffffffffffL)。



Java中的小數精確計算
Java程式碼

   1. public class FloatCalc { 
   2.  
   3.     //預設除法運算精度    
   4.     private static final int DEF_DIV_SCALE = 10; 
   5.  
   6.     /**   
   7.     *   提供精確的加法運算。   
   8.     *   @param   v1   被加數   
   9.     *   @param   v2   加數   
  10.     *   @return   兩個引數的和   
  11.     */ 
  12.  
  13.     public static double add(double v1, double v2) { 
  14.         BigDecimal b1 = new BigDecimal(Double.toString(v1)); 
  15.         BigDecimal b2 = new BigDecimal(Double.toString(v2)); 
  16.         return b1.add(b2).doubleValue(); 
  17.     } 
  18.  
  19.     /**   
  20.     *   提供精確的減法運算。   
  21.     *   @param   v1   被減數   
  22.     *   @param   v2   減數   
  23.     *   @return   兩個引數的差   
  24.     */ 
  25.  
  26.     public static double sub(double v1, double v2) { 
  27.         BigDecimal b1 = new BigDecimal(Double.toString(v1)); 
  28.         BigDecimal b2 = new BigDecimal(Double.toString(v2)); 
  29.         return b1.subtract(b2).doubleValue(); 
  30.     } 
  31.  
  32.     /**   
  33.       *   提供精確的乘法運算。   
  34.       *   @param   v1   被乘數   
  35.       *   @param   v2   乘數   
  36.       *   @return   兩個引數的積   
  37.       */ 
  38.  
  39.     public static double mul(double v1, double v2) { 
  40.         BigDecimal b1 = new BigDecimal(Double.toString(v1)); 
  41.         BigDecimal b2 = new BigDecimal(Double.toString(v2)); 
  42.         return b1.multiply(b2).doubleValue(); 
  43.     } 
  44.  
  45.     /**   
  46.       *   提供(相對)精確的除法運算,當發生除不盡的情況時,精確到   
  47.       *   小數點以後10位,以後的數字四捨五入。   
  48.       *   @param   v1   被除數   
  49.       *   @param   v2   除數   
  50.       *   @return   兩個引數的商   
  51.       */ 
  52.  
  53.     public static double div(double v1, double v2) { 
  54.         return div(v1, v2, DEF_DIV_SCALE); 
  55.     } 
  56.  
  57.     /**   
  58.       *   提供(相對)精確的除法運算。當發生除不盡的情況時,由scale引數指   
  59.       *   定精度,以後的數字四捨五入。   
  60.       *   @param   v1   被除數   
  61.       *   @param   v2   除數   
  62.       *   @param   scale   表示表示需要精確到小數點以後幾位。   
  63.       *   @return   兩個引數的商   
  64.       */ 
  65.  
  66.     public static double div(double v1, double v2, int scale) { 
  67.         if (scale < 0) { 
  68.             throw new IllegalArgumentException( 
  69.                     "The scale must be a positive integer or zero"); 
  70.         } 
  71.         BigDecimal b1 = new BigDecimal(Double.toString(v1)); 
  72.         BigDecimal b2 = new BigDecimal(Double.toString(v2)); 
  73.         return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); 
  74.     } 
  75.  
  76.     /**   
  77.       *   提供精確的小數位四捨五入處理。   
  78.       *   @param   v   需要四捨五入的數字   
  79.       *   @param   scale   小數點後保留幾位   
  80.       *   @return   四捨五入後的結果   
  81.       */ 
  82.  
  83.     public static double round(double v, int scale) { 
  84.         if (scale < 0) { 
  85.             throw new IllegalArgumentException( 
  86.                     "The scale must be a positive integer or zero"); 
  87.         } 
  88.         BigDecimal b = new BigDecimal(Double.toString(v)); 
  89.         BigDecimal one = new BigDecimal("1"); 
  90.         return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); 
  91.     } 
  92. }

定點數表達法的缺點在於其形式過於僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利於同時表達特別大的數或者特別小的數。
計算機系統採納了所謂的浮點數表達方式。這種表達方式利用科學計數法來表達實數,即用一個尾數(Mantissa也叫有效數字 ),一個基數(Base),一個指數(Exponent)以及

一個表示正負的符號來表達實數。浮點數利用指數達到了浮動小數點的效果,從而可以靈活地表達更大範圍的實數。


當一個浮點數的尾數為0,不論其階碼為何值,該浮點數的值都為0。當階碼的值為它能表示的最小一個值或更小的值時,不管其尾數為何值,計算機都把該浮點數看成零值,通常稱

其為機器零,此時該浮點數的所有各位(包括階碼位和尾數位)都清為0值。

Java 平臺上的浮點數型別 float 和 double 採納了 IEEE 754 標準中所定義的單精度 32 位浮點數和雙精度 64 位浮點數的格式。

在 IEEE 標準中,浮點數是將特定長度的連續位元組的所有二進位制位分割為特定寬度的符號域,指數域和尾數域三個域,其中儲存的值分別用於表示給定二進位制浮點數中的符號,指

數和尾數。這樣,通過尾數和可以調節的指數就可以表達給定的數值了。具體的格式參見下面的圖例:




在上面的圖例中,第一個域為符號域。其中 0 表示數值為正數,而 1 則表示負數。

第二個域為指數域,對應於我們之前介紹的二進位制科學計數法中的指數部分。其中單精度數為 8 位,雙精度數為 11 位。以單精度數為例,8 位的指數為可以表達 0 到 255 之間

的 255 個指數值(注:指數8位的最高位都是數值位,沒有符號位)。但是,指數可以為正數,也可以為負數。為了處理負指數的情況,實際的指數值按要求需要加上一個偏差

(Bias)值作為儲存在指數域中的值,單精度數的偏差值為 127(2^7-1),而雙精度數的偏差值為1023(2^10-1)。比如,單精度的實際指數值 0 在指數域中將儲存為 127;而

儲存在指數域中的 64 則表示實際的指數值 -63(64-127=-63)。 偏差的引入使得對於單精度數,實際可以表達的指數值的範圍就變成 -127(表示小數點需向左移動127位) 到

128(表示小數點需向右移動128位) 之間(包含兩端)。我們不久還將看到,實際的指數值 -127(全 0)以及 +128(全 1)保留用作特殊值的處理。這樣,實際可以表達的有效

指數範圍就在 -127 和 127 之間。

第三個域為尾數域,其中單精度數為 23 位長,雙精度數為 52 位長。除了我們將要講到的某些特殊值外,IEEE 標準要求浮點數必須是規範的。這意味著尾數的小數點左側必須為

1,因此我們在儲存尾數的時候,可以省略小數點前面這個 1,從而騰出一個二進位制位來儲存更多的尾數。這樣我們實際上用 23 位長的尾數域表達了 24 位的尾數。比如對於單精

度數而言,二進位制的 1001.101(對應於十進位制的 9.625)可以表達為 1.001101 × 2^3,所以實際儲存在尾數域中的值為 00110100000000000000000,即去掉小數點左側的 1,並

用 0 在右側補齊。
值得注意的是,對於單精度數,由於我們只有 24 位的尾數(其中一位隱藏),所以可以表達的最大尾數為 2^24- 1 = 16,777,215。特別的,16,777,216 是偶數,所以我們可以

通過將它除以 2 並相應地調整指數來儲存這個數,這樣 16,777,216 同樣可以被精確的儲存。相反,數值 16,777,217 則無法被精確的儲存。由此,我們可以看到單精度的浮點數

可以表達的十進位制數值中,真正有效的數字不高於 8 位。事實上,對相對誤差的數值分析結果顯示有效的精度大約為 7.22 位(由於位數不可取小數,所以單精度的精度為7,即

可精確到小數點後7位)。參考下面的示例:
Java程式碼

   1. System.out.println(16777215f);//1.6777215E7 
   2. System.out.println(16777216f);//1.6777216E7 
   3. System.out.println(16777217f);//1.6777216E7 
   4. System.out.println(16777218f);//1.6777218E7 
   5. System.out.println(16777219f);//1.677722E7 
   6. System.out.println(16777220f);//1.677722E7 
   7. System.out.println(16777221f);//1.677722E7 
   8. System.out.println(16777222f);//1.6777222E7 
   9. System.out.println(16777223f);//1.6777224E7 
  10. System.out.println(16777224f);//1.6777224E7 
  11. System.out.println(16777225f);//1.6777224E7 

  System.out.println(16777215f);//1.6777215E7
  System.out.println(16777216f);//1.6777216E7
  System.out.println(16777217f);//1.6777216E7
  System.out.println(16777218f);//1.6777218E7
  System.out.println(16777219f);//1.677722E7
  System.out.println(16777220f);//1.677722E7
  System.out.println(16777221f);//1.677722E7
  System.out.println(16777222f);//1.6777222E7
  System.out.println(16777223f);//1.6777224E7
  System.out.println(16777224f);//1.6777224E7
  System.out.println(16777225f);//1.6777224E7

請看結果推導分析:

111111111111111111111111          16777215f
1.11111111111111111111111          剛好是23位,不會丟失精度,能精確表示
0 23+127 11111111111111111111111
0 10010110 11111111111111111111111



1000000000000000000000000         16777216f
1.00000000000000000000000 0        去掉的是0,所以還是能準確表示
0 24+127 00000000000000000000000
0 10010111 00000000000000000000000



1000000000000000000000001          16777217f
1.00000000000000000000000 1        不能準確表示。先試著進位
1.00000000000000000000001          由於進位後,結果的最末們不是0,所以直接舍掉
1.00000000000000000000000          到這裡結果就是16777216f



1000000000000000000000010         16777