0.1+0.2=0.30000000000000004問題的探究

爲什麼“0.1+0.2=0.30000000000000004”? 


首先聲明這不是bug,原因在與十進制到二進制的轉換導致的精度問題! 


 


其次這幾乎出現在很多的編程語言中:C/C++,Java,Javascript中,準確的說:“使用了IEEE 754浮點數格式”來存儲浮點類型(float 32,double 64)的任何編程語言都有這個問題! 


 


 


 


簡要介紹下IEEE 754浮點格式:它用科學記數法以底數爲2的小數來表示浮點數。IEEE浮點數(共32位)用1位表示數字符號,用8爲表示指數,用23爲來表示尾數(即小數部分)。此處指數用移碼存儲,尾數則是原碼(沒有符號位)。之所以用移碼是因爲移碼的負數的符號位爲0,這可以保證浮點數0的所有位都是0。 


 


雙精度浮點數(64位),使用1爲符號位、11位指數位、52位尾數位來表示。 


 


因爲科學記數法有很多種方式來表示給定的數字,所以要規範化浮點數,以便用底數爲2並且小數點左邊爲1的小數來表示(注意是二進制的,所以只要不爲0則一定有一位爲1),按照需要調節指數就可以得到所需的數字。 


 


例如:十進制的1.25 =  二進制的1.01 =  則存儲時指數爲0、尾數爲1.01、符號位爲0. 


 


(十進制轉二進制) 


 


 


 


回到開頭,爲什麼“0.1+0.2=0.30000000000000004”?首先聲明這是javascript語言計算的結果(注意Javascript的數字類型是以64位的IEEE 754格式存儲的)。 


 


正如同十進制無法精確表示1/3(0.33333…)一樣,二進制也有無法精確表示的值。例如1/10。 


 


64位浮點數情況下: 


 


十進制0.1 


 


=  二進制0.00011001100110011…(循環0011) 


 


= 尾數爲1.1001100110011001100…1100(共52位,除了小數點左邊的1),指數爲-4(二進制移碼爲00000000010),符號位爲0 


 


=  存儲爲:0 00000000100 10011001100110011…11001 


 


=  因爲尾數最多52位,所以實際存儲的值爲0.00011001100110011001100110011001100110011001100110011001 


 


十進制0.2 


 


=  二進制0.0011001100110011…(循環0011) 


 


= 尾數爲1.1001100110011001100…1100(共52位,除了小數點左邊的1),指數爲-3(二進制移碼爲00000000011),符號位爲0 


 


=  存儲爲:0 00000000011 10011001100110011…11001 


 


因爲尾數最多52位,所以實際存儲的值爲0.00110011001100110011001100110011001100110011001100110011 


 


 


 


     0.00011001100110011001100110011001100110011001100110011001 


 


+  0.00110011001100110011001100110011001100110011001100110011 


 


=  0.01001100110011001100110011001100110011001100110011001100 


 


轉換成10進制之後得到:0.30000000000000004! 


 


 


 


浮點數中的特殊數字 


除了一般範圍內的數字之外,還有一些特殊數字:無窮大、負無窮大、-0和NaN(“代表不是數字”)。造成了如下一些特殊情況: 


 


public class FloatTest { 


 public static void main(String[] args) { 


 System.out.println(0.1f+0.2f); //0.3 


 System.out.println(0.1d+0.2d); //0.30000000000000004 


 System.out.println(Math.sqrt(-1.0)); //NaN 


 System.out.println(0.0 / 0.0);//NaN 


 System.out.println(1.0 / 0.0);//Infinity 


 System.out.println(-1.0 / 0.0);//-Infinity 


 System.out.println(0.0 / 0.0 + 1.0);//NaN + 1.0 = NaN 


 System.out.println(1.0 / 0.0 + 1.0);//無窮大 + 1.0 = Infinity 


 System.out.println(1.0 / 0.0 + 1.0 / 0.0);//無窮大 + 無窮大 = Infinity 


 System.out.println(0.0 / 0.0   1.0);//NaN   1.0 = false 


 System.out.println(0.0 / 0.0 == 1.0);//NaN == 1.0 = false 


 System.out.println(0.0 / 0.0   1.0);//NaN   1.0 = false 


 System.out.println(0.0 / 0.0 == 0.0 / 0.0);//NaN == NaN = false 


 System.out.println(0.0 == -0.01); //false 


 } 



 


 


 


更精確的計算 


既然一般的浮點數計算有這麼多問題,那麼如何實現更精確的計算呢? 


 


Java中提供了BigDecimal類實現基於十進制的浮點數計算。 


 


在Javascript 2(目前瀏覽器不支持)中提供一種use decimal;實現十進制浮點數計算: 


 



  use decimal; 


 


  var a = 0.1;    //  a is a decimal 


  var b = 0.2;    //  b is a decimal 


  var c = a + b;  //  c is a decimal (0.3) 



 


var d = 0.1 + 0.2;  //  d is a double (0.30000000000000004) 


 


var a = 0.1m;   //  a is a decimal 


var b = 0.2m;   //  b is a decimal 


var c = a + b;  //  c == 0.3m更詳細的JS的十進制計算方法。 


 


C#也支持如上的m操作符實現十進制浮點數計算。 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章