IEEE 754 浮點數的詳細分析

前言

從網上看到不少程序員對浮點數精度問題有很多疑問,在論壇上發貼詢問,很多熱心人給予瞭解答,但我發現一些解答中有些許小的錯誤和認識不當之處。我曾經做過數值算法程序,雖然基本可用,但是被浮點數精度問題所困擾;事情過後,我花了一點時間蒐集資料,並仔細研究,有些心得體會,願意與大家分享,希望對IEEE 754標準中的二進制浮點數精度及其相關問題給予較爲詳盡的解釋。當然,文中任何錯誤由本人造成,由我承擔,特此聲明。

1、 什麼是IEEE 754標準?

目前支持二進制浮點數的硬件和軟件文檔中,幾乎都聲稱其浮點數實現符合IEEE 754標準。那麼,什麼是IEEE 754標準?

最權威的解釋是IEEE754標準本身ANSI/IEEE Std 754-1985《IEEE Standard for Binary Floating-Point Arithmetic》,網上有PDF格式的文件,Google一下,下載即可。標準文本是英文的,總共才23頁,有耐心的話可以仔細閱讀。這裏摘錄前言中的一句:

This standard defines a family of commercially feasible ways for new systems to perform binary floating-point arithmetic。

其實是句廢話,什麼也沒說。

IEEE 754標準的主要起草者是加州大學伯克利分校數學系教授William Kahan,他幫助Intel公司設計了8087浮點處理器(FPU),並以此爲基礎形成了IEEE 754標準,Kahan教授也因此獲得了1987年的圖靈獎。讚一句:IEEE 754浮點格式確實是天才的設計。Kahan教授的主頁:http://www.cs.berkeley.edu/~wkahan/

看看其它文獻怎麼說。

2、 IEEE 754標準規定了什麼?

以下內容來自Sun公司的《Numerical Computation Guide-Sun Studio 11》的中文版《數值計算指南》,並加上本人的一點說明。說實話,該中文指南翻譯得不太好,例如,round譯成“四捨五入”。

IEEE 754 規定:

a) 兩種基本浮點格式:單精度和雙精度。

IEEE單精度格式具有24位有效數字,並總共佔用32 位。IEEE雙精度格式具有53位有效數字精度,並總共佔用64位。

說明:基本浮點格式是固定格式,相對應的十進制有效數字分別爲7位和17位。基本浮點格式對應的C/C++類型爲float和double。

b) 兩種擴展浮點格式:單精度擴展和雙精度擴展。

此標準並未規定擴展格式的精度和大小,但它指定了最小精度和大小。例如,IEEE 雙精度擴展格式必須至少具有64位有效數字,並總共佔用至少79 位。

說明:雖然IEEE 754標準沒有規定具體格式,但是實現者可以選擇符合該規定的格式,一旦實現,則爲固定格式。例如:x86 FPU是80位擴展精度,而Intel安騰FPU是82位擴展精度,都符合IEEE 754標準的規定。C/C++對於擴展雙精度的相應類型是long double,但是,Microsoft Visual C++ 6.0版本以上的編譯器都不支持該類型,long double和double一樣,都是64位基本雙精度,只能用其它C/C++編譯器或彙編語言。

c) 浮點運算的準確度要求:加、減、乘、除、平方根、餘數、將浮點格式的數舍入爲整數值、在不同浮點格式之間轉換、在浮點和整數格式之間轉換以及比較。

求餘和比較運算必須精確無誤。其他的每種運算必須向其目標提供精確的結果,除非沒有此類結果,或者該結果不滿足目標格式。對於後一種情況,運算必須按照下面介紹的規定舍入模式的規則對精確結果進行最低限度的修改,並將經過此類修改的結果提供給運算的目標。

說明:IEEE 754沒有規定基本算術運算(+、-、×、/ 等)的結果必須精確無誤,因爲對於IEEE 754的二進制浮點數格式,由於浮點格式長度固定,基本運算的結果幾乎不可能精確無誤。這裏用三位精度的十進制加法來說明:

例1:a = 3.51,b = 0.234,求a+b = ?

a與b都是三位有效數字,但是,a+b的精確結果爲3.744,是四位有效數字,對於該浮點格式只有三位精度,a+b的結果無法精確表示,只能近似表示,具體運算結果取決於舍入模式(見舍入模式的說明)。同理,由於浮點格式固定,對於其他基本運算,結果也幾乎無法精確表示。

d) 在十進制字符串和兩種基本浮點格式之一的二進制浮點數之間進行轉換的準確度、單一性和一致性要求。

對於在指定範圍內的操作數,這些轉換必鬚生成精確的結果(如果可能的話),或者按照規定舍入模式的規則,對此類精確結果進行最低限度的修改。對於不在指定範圍內的操作數,這些轉換生成的結果與精確結果之間的差值不得超過取決於舍入模式的指定誤差。

說明:這一條規定是針對十進制字符串表示的數據與二進制浮點數之間相互轉換的規定,也是一般編程者最容易產生錯覺的事情。因爲人最熟悉的是十進制,以爲對於任意十進制數,二進制都應該能精確表示,其實不然。本文主要目的就是揭密二進制浮點數所能夠精確表示的十進制數,如果你以前沒有想過這個問題,絕對讓你喫驚。賣個關子先!

e) 五種類型的IEEE 浮點異常,以及用於向用戶指示發生這些類型異常的條件。

五種類型的浮點異常是:無效運算、被零除、上溢、下溢和不精確。

說明:關於浮點異常,見Kahan教授的《Lecture Notes on IEEE 754》,這裏我就不浪費口水了。

f) 四種舍入方向:

向最接近的可表示的值;當有兩個最接近的可表示的值時首選“偶數”值;向負無窮大(向下);向正無窮大(向上)以及向0(截斷)。

說明:舍入模式也是比較容易引起誤解的地方之一。我們最熟悉的是四捨五入模式,但是,IEEE 754標準根本不支持,它的默認模式是最近舍入(Round to Nearest),它與四捨五入只有一點不同,對.5的舍入上,採用取偶數的方式。舉例比較如下:

例2:

最近舍入模式:Round(0.5) = 0; Round(1.5) = 2; Round(2.5) = 2;

四捨五入模式:Round(0.5) = 1; Round(1.5) = 2; Round(2.5) = 3;

主要理由:由於字長有限,浮點數能夠精確表示的數是有限的,因而也是離散的。在兩個可以精確表示的相鄰浮點數之間,必定存在無窮多實數是IEEE浮點數所無法精確表示的。如何用浮點數表示這些數,IEEE 754的方法是用距離該實數最近的浮點數來近似表示。但是,對於.5,它到0和1的距離是一樣近,偏向誰都不合適,四捨五入模式取1,雖然銀行在計算利息時,願意多給0.5分錢,但是,它並不合理。例如:如果在求和計算中使用四捨五入,一直算下去,誤差有可能越來越大。機會均等才公平,也就是向上和向下各佔一半才合理,在大量計算中,從統計角度來看,高一位分別是偶數和奇數的概率正好是50% : 50%。至於爲什麼取偶數而不是奇數,大師Knuth有一個例子說明偶數更好,於是一錘定音。最近舍入模式在C/C++中沒有相應的函數,當然,IEEE754以及x86 FPU的默認舍入模式是最近舍入,也就是每次浮點計算結果都採用最近舍入模式,除非用程序顯式設置爲其它三種舍入模式。

另外三種舍入模式,簡要說明。

向0(截斷)舍入:C/C++的類型轉換。(int) 1.324 = 1,(int) -1.324 = -1;

向負無窮大(向下)舍入:C/C++函數floor()。例如:floor(1.324) = 1,floor(-1.324) = -2。

向正無窮大(向上)舍入:C/C++函數ceil()。ceil(1.324) = 2。Ceil(-1.324) = -1;

後兩種舍入方法據說是爲了數值計算中的區間算法,但很少聽說哪個商業軟件使用區間算法。

3、 十進制小數與二進制小數的相互轉換

先看看十進制數與二進制數如何互相轉換。用下標表示數的基(base),即d10表示十進制數,b2二進制數。則一個具有n+1位整數m位小數的十進制數d10表示爲:

例3:

同理,一個具有n+ 1位整數m位小數的二進制數b2表示爲:

例4:

二進制數轉換成十進制數,比較容易,如例4。

十進制數轉換成二進制數,是把整數部分和小數部分分別轉換,整數部分用2除,取餘數,小數部分用2乘,取整數位。

例5:把(13.125)10轉換成二進制數

整數部分:,小數部分:

因此,

說明:C/C++語言的scanf()函數一般不採用這種方法。

一個十進制數能否用二進制浮點數精確表示,關鍵在於小數部分。我們來看一個最簡單的小數能否精確表示。按照乘以2取整數位的方法,有:

得到一個無限循環的二進制小數,用有限位無法表示無限循環小數,因此,無法用IEEE 754浮點數精確表示。從中也可以看到:由於

這四個數也無法精確表示。同理:

也無法用IEEE 754浮點數精確表示。

結論1:的9個小數中,只有0.5可以精確表示:

可以把這個結論推廣到一般情況:

結論2:任何下面的十進制數都無法用IEEE 754浮點數精確表示,必定存在誤差。

如果的整數部分能精確表示且該數在浮點數的精度範圍之內,則該數可以精確表示。

4、 二進制小數能精確表示的十進制小數的基本規律

上述結論是由十進制數向二進制數轉換而得到的,下面從二進制數向十進制數轉換的角度來推演:

可以一直算下去,得到一個基本規律

結論3:一個十進制小數要能用浮點數精確表示,最後一位必須是5,因爲1 除以2永遠是0.5,當然這是必要條件,並非充分條件。

一個m位二進制小數能夠精確表示的十進制小數有多少個呢?當然是個。推演如下:

一位二進制小數能夠精確表示的小數只有個:

兩位二進制小數能夠精確表示的小數有個:

三位二進制小數能夠精確表示的小數有個:

m位二進制小數能夠精確表示的十進制小數就是個。而m位十進制小數有個,因此,能精確表示的十進制小數的比例是,m越大,比例越小。以常用的單精度和雙精度浮點數爲例,m分別是24和53,則比例爲:,小到可以忽略不計。

5、 FAQ:C/C++庫函數函數printf() 是如何忽悠我們的?

Q:既然絕大部分浮點小數都不能精確表示十進制小數,爲什麼printf()經常能打印出準確的值?

A:因爲IEEE 754對二進制到十進制的轉換有明確規定,見前面2.d)。而且函數printf()默認情況下只打印7位有效數字,在誤差不大的情況下是沒有問題的,但是,我們經常見到這樣的結果“.xxxx999999”。用printf(“%.17lf”, …);可以讓浮點數顯出原形。

6、 與IEEE 754相關的標準

本文的結論基於IEEE 754標準,另外一個標準是IEEE 854,這個標準是關於十進制浮點數的,但沒有規定具體格式,所以很少被採用。另外,從2000年開始,IEEE 754開始修訂,被稱爲IEEE 754R(http://754r.ucbtest.org/),目的是融合IEEE 754和IEEE 854標準,已經在工作組內進行表決,還沒有被IEEE表決通過,估計也快了。該標準在浮點格式方面的修訂如下:

a) 加入了16位和128位的二進制浮點數格式。

b) 加入了十進制浮點數格式,採用了IBM公司(http://www2.hursley.ibm.com/decimal/)提出的格式,Intel公司也提出了自己的格式,但未被採納,只留了口子。(標準從來都是企業利益博弈的產物)。

7、 是否該使用十進制浮點數?

Kahan教授的看法:一定要使用十進制浮點數,以避免人爲錯誤。也就是這種錯誤:double d = 0.1;實際上,d≠0.1。

IBM公司的看法:在經濟、金融和與人相關的程序中,使用十進制浮點數。但是,由於沒有硬件支持,用軟件實現的十進制浮點計算比硬件實現的二進制浮點計算要慢100-1000倍。由於被IEEE 754R所採納,IBM公司將在下一代Power芯片中實現十進制FPU。(http://www2.hursley.ibm.com/decimal/

8、 進一步閱讀建議

本文討論的是二進制浮點數的表示精度問題,對於計算精度,可以閱讀David Goldberg的經典文章《What Every Computer Scientist Should Know About Floating-Point Arithmetic》,別以爲“Scientist”是什麼高級玩意兒,在這裏是“初學者”,《數值計算指南》把該文作爲附錄。

總結

精確是偶然的,誤差是必然的。如果做數值算法,惟一能做的就是誤差不積累,其它的就不要奢望了。

轉:http://www.cnblogs.com/bossin/archive/2007/04/08/704567.html

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