1. 程式人生 > >IEEE 754 浮點數的表示精度探討

IEEE 754 浮點數的表示精度探討

選擇 固定 images 方向 post 可用 分用 lan text

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) 在十進制字符串和兩種基本浮點格式之中的一個的二進制浮點數之間進行轉換的精確度、單一性和一致性要求。

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

說明:這一條規定是針對十進制字符串表示的數據與二進制浮點數之間相互轉換的規定。也是一般編程者最easy產生錯覺的事情。

由於人最熟悉的是十進制,以為對於隨意十進制數,二進制都應該能精確表示。事實上不然。本文主要目的就是揭密二進制浮點數所可以精確表示的十進制數,假設你曾經沒有想過這個問題。絕對讓你驚訝。賣個關子先。

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

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

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

f) 四種舍入方向:

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

說明:舍入模式也是比較easy引起誤解的地方之中的一個。

我們最熟悉的是四舍五入模式,可是,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:

技術分享

二進制數轉換成十進制數。比較easy,如例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”是什麽高級玩意兒。在這裏是“剛開始學習的人”,《數值計算指南》把該文作為附錄。

總結

精確是偶然的,誤差是必定的。

假設做數值算法,惟一能做的就是誤差不積累,其他的就不要奢望了。

IEEE 754 浮點數的表示精度探討