1. 程式人生 > >[轉載]float,double等精度丟失問題

[轉載]float,double等精度丟失問題

問題提出:12.0f-11.9f=0.10000038,"減不盡"為什麼?

來自MSDN的解釋:

為何浮點數可能丟失精度浮點十進位制值通常沒有完全相同的二進位制表示形式。 這是 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,double精度丟失問題

問題提出:12.0f-11.9f=0.10000038,"減不盡"為什麼? 來自MSDN的解釋: 為何浮點數可能丟失精度浮點十進位制值通常沒有完全相同的二進位制表示形式。 這是 CPU 所採用的浮點資料表示形式的副作用。 為此,可能會經歷一些精度丟失,並且一些浮點

float,double精度丟失問題

idt multipl sof zh-cn hid lose 減法 add hide 問題提出:   12.0f-11.9f = 0.10000038,"減不盡"為什麽?   8888.88*100 = 888887.9999999999 ??? 來自MSDN的解釋:h

對於doublefloat型別運算精度丟失,請使用BigDecimal

一、簡介 Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數進行精確的運算。雙精度浮點型變數double可以處理16位有效數。在實際應用中,需要對更大或者更小的數進行運算和處理。float和double只能用來做科學計算或者是工程計算,在商業計算中要用java.m

Java中double型別精度丟失問題

  首先,之所以double浮點數型別中會出現精度不準確這個問題,是因為浮點數在計算機的儲存方法導致的,要想具體瞭解的話,可以檢視《計算機組成原理》這本書,裡面有比較詳盡的解答。   接下來由一道題來引出接下來要講的這個問題:   對任意給定的一個doubl

關於double計算精度丟失解決辦法

import java.math.BigDecimal; public class Arith { // 原始檔Arith.java: /** * 由於Java的簡單型別不能夠精確的對浮點數進行運算,這個工具類提供精 確的浮點數運算,包括加減乘除和四捨五入。

Java:利用BigDecimal類巧妙處理Double型別精度丟失

[toc] ## 本篇要點 - 簡單描述浮點數十進位制轉二進位制精度丟失的原因。 - 介紹幾種建立BigDecimal方式的區別。 - 整理了高精度計算的工具類。 - 學習了阿里巴巴Java開發手冊關於BigDecimal比較相等的規定。 ## 經典問題:浮點數精度丟失 精度丟失的問題是在其他計算機語

doublefloat型別進行運算造成精度丟失的問題解決

在操作的過程中,我們無意中會造成精度丟失。遇到這個問題我們進行如何解決呢?大家不要慌,本文是來介紹精度丟失的問題。 我們來看一下如下的程式碼: double price1 = 10.01d; double price2 =

floatdouble精度丟失問題

解決一個價格轉換顯示的bug double a=Double.parseDouble(3.80); long price=new Double(a*100).longValue();結果是3.799999999.......大部分程

【GreenDao學習筆記】SQLite資料庫儲存float/double小數型別精度丟失

       用GreenDao工具來操作資料庫明顯提高了開發效率,但是也會出現一些小問題,比如儲存小數的時候,加加減減總無可避免精度不準,SQLite的資料型別只有這麼幾種(參考:https://www.sqlite.org/datatype3.html)。BigDecim

關於Java中用Double型運算時精度丟失的問題

左右 數字 col cto into lam 決定 action 簡化 註:轉自 https://blog.csdn.net/bleach_kids/article/details/49129943 在使用Java,double 進行運算時,經常出現精度丟失的問題,總是在

關於floatdouble計算精度缺失解決方法筆記

最近在專案中的財務管理模組遇到一個問題就是資料庫欄位建的型別是float,在計算後會引起精度缺失問題 拋開資料庫建立的不當以外,遇到這個問題的解決方法如下: 例子: public static void main(String[] args) { // TODO Auto-gene

float double位數(精度)解析

1. 範圍   float和double的範圍是由指數的位數來決定的。   float的指數位有8位,而double的指數位有11位,分佈如下:   float:   1bit(符號位) 8bits(指數位) 23bits(尾數位)   double:   1bit(符號位)

Unity,C# float轉String如何保留實際小數位數,即防止精度丟失擷取字串報錯

比如一個float a = 329.0; 轉成String,要讓a顯示成329.0;如果a = 329.000,轉成String 顯示成329.000 可是實際卻不是這樣的 a.ToString()直接變成329 所以碰到這種情況怎麼辦呢 一. string Dat

C++ float資料精度丟失問題

將float、double轉換為字元過程中可能存在精度損失,但是通過%.8lf可以規避 (1)float小數點前後加起來有效數字只有6位。當給定的float有效數在6位以內轉換為字元不會丟失精度,當有效位數大於6位就會存在精度丟失     //精度沒有丟失     char

floatdouble精度計算

Java浮點數的取值範圍與其精度,必須先了解浮點數的表示方法,浮點數的結構組成:由符號位,指數位,尾數位組成。 Java中浮點數採用的是IEEE 754標準。 一個float4位元組32位,分為三部分:符號位,指數位,尾數位 符號位(S):最高位(31位)為符號

java double型別加減運算時出現的精度丟失問題

問題由來: 今天在寫一個業務引數的時候,需要幾個數一起算一下最後返回一個浮點數。 一開始我就直接用強制型別轉化之後開始加加減減,後來發現總是會在末尾多出一些莫名的小數,這很明顯就是精度丟失問題,但是因為是要返回固定的資料型別,不能用Format轉成Strin

java中double型別資料加減操作精度丟失問題及解決方法

double型別資料加減操作精度丟失問題 今天在專案中用到double型別資料加減運算時,遇到了一個奇怪的問題,比如1+20.2+300.03,理論上結果應該是321.23,其實結果並不是這樣。 public double add() {

java中Double型別的運算精度丟失的問題 (小數點多出99999999999999)

 在使用Java,double 進行運算時,經常出現精度丟失的問題,總是在一個正確的結果左右偏0.0000**1。 特別在實際專案中,通過一個公式校驗該值是否大於0,如果大於0我們會做一件事情,小於0我們又處理其他事情。 這樣的情況通過double計算出來的結果去和0比較

double*指標型別所佔位元組數 float* long* int* short* 要求用sizeof 運算子計算C++中char*

因為指標就是地址 都是4個位元組 sizeof(x1));... ||| 32BIT作業系統的 的PC裡 所有的指標 指標所佔的位元組數都是機器的位數32位的機器佔32/8=4位元組不用計算了吧cout&

Java中float/double取值範圍與精度

Java浮點數 浮點數結構   要說清楚Java浮點數的取值範圍與其精度,必須先了解浮點數的表示方法,浮點數的結構組成,之所以會有這種所謂的結構,是因為機器只認識01,你想表示小數,你要機器認識小數點這個東西,必須採用某種方法,比如,簡單點的,float