IEEE讀做[aɪ-'trɪp(ə)l-i:],電氣和電子工程師協會。這個是一個包括全部電子和計算機技術的專業團體,制定標準是其工做之一。java
IEEE浮點數算術標準(IEEE 754)是最普遍使用的浮點數運算標準,爲許多CPU與浮點運算器所採用。程序員
整數運算和浮點數運算會有不一樣的數學屬性是由於它們處理數字表示有限性的方式不一樣——整數的表示算然只能編碼一個相對較小的數字範圍,可是這種表示是精確的;而浮點數雖然能夠編碼一個較大的數值範圍,可是這種表示只是近似的。算法
IEEE規定的浮點數編碼會將一個浮點數轉換爲二進制數。以科學計數法劃分,將浮點數拆分爲3部分:符號、指數、尾數。編程
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 浮點異常,以及用於向用戶指示發生這些類型異常的條件。
五種類型的浮點異常是:無效運算、被零除、上溢、下溢和不精確。
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;
後兩種舍入方法聽說是爲了數值計算中的區間算法,但不多據說哪一個商業軟件使用區間算法。
浮點數
在計算機系統的發展過程當中,曾經提出過多種方法表達實數。典型的好比相對於浮點數的定點數(Fixed Point Number)。在這種表達方式中,小數點固定的位於實數全部數字中間的某個位置。貨幣的表達就可使用這種方式,好比 99.00 或者 00.99 能夠用於表達具備四位精度(Precision),小數點後有兩位的貨幣值。因爲小數點位置固定,因此能夠直接用四位數值來表達相應的數值。SQL 中的 NUMBER 數據類型就是利用定點數來定義的。還有一種提議的表達方式爲有理數表達方式,即用兩個整數的比值來表達實數。
定點數表達法的缺點在於其形式過於僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利於同時表達特別大的數或者特別小的數。最終,絕大多數現代的計算機系統採納了所謂的浮點數表達方式。這種表達方式利用科學計數法來表達實數,即用一個尾數(Mantissa,尾數有時也稱爲有效數字——Significand;尾數其實是有效數字的非正式說法),一個基數(Base),一個指數(Exponent)以及一個表示正負的符號來表達實數。好比 123.45 用十進制科學計數法能夠表達爲 1.2345 × 102 ,其中1.2345 爲尾數,10 爲基數,2 爲指數。浮點數利用指數達到了浮動小數點的效果,從而能夠靈活地表達更大範圍的實數。
計算機中是用有限的連續字節保存浮點數的。在 IEEE 標準中,浮點數是將特定長度的連續字節的全部二進制位分割爲特定寬度的符號域,指數域和尾數域三個域,其中保存的值分別用於表示給定二進制浮點數中的符號,指數和尾數。這樣,經過尾數和能夠調節的指數(因此稱爲"浮點")就能夠表達給定的數值了。
IEEE 754 指定:
n 兩種基本的浮點格式:單精度和雙精度。
n 兩種擴展浮點格式:單精度擴展和雙精度擴展。此標準並未規定這些格式的精確精度和和大小,但它指定了最小精度和大小。例如,IEEE 雙精度擴展格式必須至少具備 64 位有效數字精度,並總共佔用至少 79 位。
具體的格式參見下面的圖例:
浮點格式是一種數據結構,用於指定包含浮點數的字段、這些字段的佈局及其算術解釋。浮點存儲格式指定如何將浮點格式存儲在內存中。IEEE 標準定義了這些格式,但具體選擇哪一種存儲格式由實現工具決定。
彙編語言軟件有時取決於所使用的存儲格式,但更高級別的語言一般僅處理浮點數據類型的語言概念。這些類型在不一樣的高級語言中具備不一樣的名稱,而且與表中所示的IEEE 格式相對應。
IEEE 精度 |
C、C++ |
Fortran (僅限 SPARC) |
單精度 |
float |
REAL 或 REAL*4 |
雙精度 |
double |
DOUBLE PRECISION 或 REAL*8 |
雙精度擴展 |
long double |
REAL*16 |
IEEE 754 明確規定了單精度浮點格式和雙精度浮點格式,併爲這兩種基本格式分別定義了一組擴展格式。表中顯示的 long double和 REAL*16 類型適用於 IEEE 標準定義的一種雙精度擴展格式。
3.1.單精度格式
IEEE 單精度格式由三個字段組成:23 位小數 f ; 8 位偏置指數 e ;以及 1 位符號 s。這些字段連續存儲在一個 32 位字中(以下圖所示)。
IEEE 標準要求浮點數必須是規範的。這意味着尾數的小數點左側必須爲 1,所以咱們在保存尾數的時候,能夠省略小數點前面這個 1,從而騰出一個二進制位來保存更多的尾數。這樣咱們實際上用 23 位長的尾數域表達了 24 位的尾數。
8 位的指數爲能夠表達 0 到 255 之間的 256 個指數值。可是,指數能夠爲正數,也能夠爲負數。爲了處理負指數的狀況,實際的指數值按要求須要加上一個誤差(Bias)值做爲保存在指數域中的值,單精度數的誤差值爲 127;誤差的引入使得對於單精度數,實際能夠表達的指數值的範圍就變成 -127 到 128 之間(包含兩端)。在本文中,最小指數和最大指數分別用 emin 和 emax 來表達。稍後將介紹實際的指數值 -127(保存爲全0)以及 +128(保存爲全 1)保留用做特殊值的處理。
3.2.雙精度格式
IEEE 雙精度格式由三個字段組成:52 位小數 f ; 11 位偏置指數 e ;以及 1 位符號s。這些字段連續存儲在兩個 32 位字中(以下圖所示)。在 SPARC 體系結構中,較高地址的 32 位字包含小數的 32 位最低有效位,而在 x86體系結構中,則較低地址的 32-位字包含小數的 32 位最低有效位。
若是用 f[31:0] 表示小數的 32 位最低有效位,則在這 32 位最低有效位中,第 0 位是整個小數的最低有效位,而第 31 位則是最高有效位。在另外一個 32 位字中, 0:19 位包含 20 位小數的最高有效位 f[51:32],其中第 0 位是這20 位最高有效位中的最低有效位,而第 19 位是整個小數的最高有效位; 20:30 位包含11 位偏置指數 e,其中第 20 位是偏置指數的最低有效位,而第 30 位是最高有效位;最高的第 31 位包含符號位 s。
上圖將這兩個連續的 32 位字按一個 64 位字那樣進行了編號,其中
IEEE 標準要求浮點數必須是規範的。這意味着尾數的小數點左側必須爲 1,所以咱們在保存尾數的時候,能夠省略小數點前面這個 1,從而騰出一個二進制位來保存更多的尾數。這樣咱們實際上用 52 位長的尾數域表達了 53 位的尾數。
11 位的指數爲能夠表達 0 到 2047 之間的2048個指數值。可是,指數能夠爲正數,也能夠爲負數。爲了處理負指數的狀況,實際的指數值按要求須要加上一個誤差(Bias)值做爲保存在指數域中的值,單精度數的誤差值爲1023;誤差的引入使得對於單精度數,實際能夠表達的指數值的範圍就變成 -1023到1024之間(包含兩端)。在本文中,最小指數和最大指數分別用 emin 和 emax 來表達。稍後將介紹實際的指數值 -1023(保存爲全0)以及 +1024(保存爲全 1)保留用做特殊值的處理。
3.3.雙精度擴展格式 (SPARC)
SPARC 浮點環境的四倍精度格式符合雙精度擴展格式的 IEEE 定義。四倍精度格式佔用 32 位字幷包含如下三個字段:112 位小數 f、15 位偏置指數 e 和 1 位符號 s。這三個字段連續存儲,如圖2-3 所示。
地址最高的 32 位字包含小數的 32 位最低有效位,用 f[31:0] 表示。緊鄰的兩個 32 位字分別包含 f[63:32] 和f[95:64]。下面的 0:15 位包含小數的 16 位最高有效位 f[111:96],其中第 0 位是這 16 位的最低有效位,而第 15 位是整個小數的最高有效位。16:30 位包含 15 位偏置指數 e,其中第 16 位是該偏置指數的最低有效位,而第 30 位是最高有效位;第 31 位包含符號位 s。
下圖將這四個連續的 32 位字按一個 128 位字那樣進行了編號,其中 0:111 位存儲小數 f ; 112:126 位存儲 15 位偏置指數 e ;而第 127 位存儲符號位 s。
3.4.雙精度擴展格式 (x86)
該浮點環境雙精度擴展格式符合雙精度擴展格式的 IEEE 定義。它包含四個字段:63 位小數 f、1 位顯式前導有效數位 j、15位偏置指數 e 以及 1 位符號 s。
在 x86 體系結構系列中,這些字段連續存儲在十個相連地址的 8 位字節中。因爲 UNIXSystem V Application Binary Interface Intel 386 Processor Supplement (Intel ABI) 要求雙精度擴展參數,從而佔用堆棧中三個相連地址的 32 位字,其中地址最高字的 16 位最高有效位未用,以下圖所示。
地址最低的 32 位字包含小數的 32 位最低有效位 f[31:0],其中第 0 位是整個小數的最低有效位,而第 31 位則是 32 位最低有效位的最高有效位。地址居中的 32 位字中,0:30 位包含小數的 31 位最高有效位 f[62:32] (其中第 0 位是這 31 位最高有效位的最低有效位,而第 30 位是整個小數的最高有效位);地址居中 32 位字的第 31 位包含顯式前導有效數位 j。
地址最高的 32 位字中,0:14 位包含 15 位偏置指數 e,其中第 0 位是該偏置指數的最低有效位,而第 14 位是最高有效位;第 15 位包含符號位 s。雖然地址最高的 32 位字的最高 16 位未被 x86 體系結構系列使用,但如上所述,它們對於符合Intel ABI 規定是相當重要的。
4.1 浮點數的規範化
一樣的數值能夠有多種浮點數表達方式,好比上面例子中的 123.45 能夠表達爲 12.345 × 101,0.12345 × 103 或者1.2345 × 102。由於這種多樣性,有必要對其加以規範化以達到統一表達的目標。規範的(Normalized)浮點數表達方式具備以下形式:
±d.dd...d × βe , (0 ≤ d i < β)
其中 d.dd...d 即尾數,β 爲基數,e 爲指數。尾數中數字的個數稱爲精度,在本文中用 p 來表示。每一個數字 d 介於0 和基數之間,包括 0。小數點左側的數字不爲 0。
基於規範表達的浮點數對應的具體值可由下面的表達式計算而得:
±(d 0 + d 1β-1 + ... + d p-1β-(p-1))βe , (0 ≤ d i < β)
對於十進制的浮點數,即基數 β 等於 10 的浮點數而言,上面的表達式很是容易理解,也很直白。計算機內部的數值表達是基於二進制的。從上面的表達式,咱們能夠知道,二進制數一樣能夠有小數點,也一樣具備相似於十進制的表達方式。只是此時 β 等於 2,而每一個數字 d 只能在 0 和 1 之間取值。好比二進制數 1001.101 至關於 1 × 2 3 + 0 × 22 + 0 × 21 + 1 × 20 + 1 × 2-1 + 0 × 2-2 + 1 × 2-3,對應於十進制的 9.625。其規範浮點數表達爲 1.001101 × 23。
4.2 根據精度表示浮點數
以上面的9.625爲例,其規範浮點數表達爲 1.001101 × 23,
所以按單精度格式表示爲:
1 10000010 00110100000000000000000
同理按雙精度格式表示爲:
1 10000000010 0011010000000000000000000000000000000000000000000000
經過前面的介紹,你應該已經瞭解的浮點數的基本知識,這些知識對於一個不接觸浮點數應用的人應該足夠了。不過,若是你興趣正濃,或者面對着一個棘手的浮點數應用,能夠經過本節瞭解到關於浮點數的一些值得注意的特殊之處。
咱們已經知道,單精度浮點數指數域實際能夠表達的指數值的範圍爲 -127 到 128 之間(包含兩端)。其中,值 -127(保存爲全0)以及 +128(保存爲全1)保留用做特殊值的處理。本節將詳細 IEEE 標準中所定義的這些特殊值。
浮點數中的特殊值主要用於特殊狀況或者錯誤的處理。好比在程序對一個負數進行開平方時,一個特殊的返回值將用於標記這種錯誤,該值爲 NaN(Not a Number)。沒有這樣的特殊值,對於此類錯誤只能粗暴地終止計算。除了 NaN 以外,IEEE 標準還定義了±0,±∞ 以及非規範化數(Denormalized Number)。
對於單精度浮點數,全部這些特殊值都由保留的特殊指數值 -127 和 128 來編碼。若是咱們分別用 emin 和 emax 來表達其它常規指數值範圍的邊界,即 -126 和 127,則保留的特殊指數值能夠分別表達爲 emin - 1 和 emax + 1; 。基於這個表達方式,IEEE 標準的特殊值以下所示:
其中 f 表示尾數中的小數點右側的(Fraction)部分。第一行即咱們以前介紹的普通的規範化浮點數。隨後咱們將分別對餘下的特殊值加以介紹。
5.1 NaN
NaN 用於處理計算中出現的錯誤狀況,好比 0.0 除以 0.0 或者求負數的平方根。由上面的表中能夠看出,對於單精度浮點數,NaN 表示爲指數爲 emax + 1 = 128(指數域全爲 1),且尾數域不等於零的浮點數。IEEE 標準沒有要求具體的尾數域,因此 NaN 實際上不是一個,而是一族。不一樣的實現能夠自由選擇尾數域的值來表達 NaN,好比 Java 中的常量 Float.NaN 的浮點數可能表達爲 01111111110000000000000000000000,其中尾數域的第一位爲 1,其他均爲 0(不計隱藏的一位),但這取決系統的硬件架構。Java 中甚至容許程序員本身構造具備特定位模式的 NaN 值(經過 Float.intBitsToFloat() 方法)。好比,程序員能夠利用這種定製的 NaN 值中的特定位模式來表達某些診斷信息。
定製的 NaN 值,能夠經過 Float.isNaN() 方法斷定其爲 NaN,可是它和 Float.NaN 常量卻不相等。實際上,全部的 NaN 值都是無序的。數值比較操做符 <,<=,> 和 >= 在任一操做數爲 NaN 時均返回 false。等於操做符 == 在任一操做數爲 NaN時均返回 false,即便是兩個具備相同位模式的 NaN 也同樣。而操做符 != 則當任一操做數爲 NaN 時返回 true。這個規則的一個有趣的結果是 x!=x 當 x 爲 NaN 時居然爲真。
能夠產生 NaN 的操做以下所示:
此外,任何有 NaN 做爲操做數的操做也將產生 NaN。用特殊的 NaN 來表達上述運算錯誤的意義在於避免了因這些錯誤而致使運算的沒必要要的終止。好比,若是一個被循環調用的浮點運算方法,可能因爲輸入的參數問題而致使發生這些錯誤,NaN 使得 即便某次循環發生了這樣的錯誤,也能夠簡單地繼續執行循環以進行那些沒有錯誤的運算。你可能想到,既然 Java 有異常處理機制,也許能夠經過捕獲並忽略異常達到相同的效果。可是,要知道,IEEE 標準不是僅僅爲 Java 而制定的,各類語言處理異常的機制不盡相同,這將使得代碼的遷移變得更加困難。況且,不是全部語言都有相似的異常或者信號(Signal)處理機制。
注意: Java 中,不一樣於浮點數的處理,整數的 0 除以 0 將拋出 java.lang.ArithmeticException 異常。
5.2 無窮
和 NaN 同樣,特殊值無窮(Infinity)的指數部分一樣爲 emax + 1 = 128,不過無窮的尾數域必須爲零。無窮用於表達計算中產生的上溢(Overflow)問題。好比兩個極大的數相乘時,儘管兩個操做數自己能夠用保存爲浮點數,但其結果可能大到沒法保存爲浮點數,而必須進行舍入。根據 IEEE 標準,此時不是將結果舍入爲能夠保存的最大的浮點數(由於這個數可能離實際的結果相差太遠而毫無心義),而是將其舍入爲無窮。對於負數結果也是如此,只不過此時舍入爲負無窮,也就是說符號域爲 1 的無窮。有了NaN 的經驗咱們不難理解,特殊值無窮使得計算中發生的上溢錯誤沒必要以終止運算爲結果。
無窮和除 NaN 之外的其它浮點數同樣是有序的,從小到大依次爲負無窮,負的有窮非零值,正負零(隨後介紹),正的有窮非零值以及正無窮。除 NaN 之外的任何非零值除以零,結果都將是無窮,而符號則由做爲除數的零的符號決定。
回顧咱們對 NaN 的介紹,當零除以零時獲得的結果不是無窮而是 NaN 。緣由不難理解,當除數和被除數都逼近於零時,其商可能爲任何值,因此 IEEE 標準決定此時用 NaN 做爲商比較合適。
5.3 有符號的零
由於 IEEE 標準的浮點數格式中,小數點左側的 1 是隱藏的,而零顯然須要尾數必須是零。因此,零也就沒法直接用這種格式表達而只能特殊處理。
實際上,零保存爲尾數域爲全爲 0,指數域爲 emin - 1 = -127,也就是說指數域也全爲 0。考慮到符號域的做用,因此存在着兩個零,即 +0 和 -0。不一樣於正負無窮之間是有序的,IEEE 標準規定正負零是相等的。
零有正負之分,的確很是容易讓人困惑。這一點是基於數值分析的多種考慮,經利弊權衡後造成的結果。有符號的零能夠避免運算中,特別是涉及無窮的運算中,符號信息的丟失。舉例而言,若是零無符號,則等式 1/(1/x) = x 當x = ±∞ 時再也不成立。緣由是若是零無符號,1 和正負無窮的比值爲同一個零,而後 1 與 0 的比值爲正無窮,符號沒有了。解決這個問題,除非無窮也沒有符號。可是無窮的符號表達了上溢發生在數軸的哪一側,這個信息顯然是不能不要的。零有符號也形成了其它問題,好比當 x=y時,等式1/x = 1/y 在 x 和 y 分別爲 +0 和 -0 時,兩端分別爲正無窮和負無窮而再也不成立。固然,解決這個問題的另外一個思路是和無窮同樣,規定零也是有序的。可是,若是零是有序的,則即便 if (x==0) 這樣簡單的判斷也因爲 x 多是 ±0 而變得不肯定了。兩害取其輕者,零仍是無序的好。
5.4 非規範化數
咱們來考察浮點數的一個特殊狀況。選擇兩個絕對值極小的浮點數,以單精度的二進制浮點數爲例,好比 1.001 × 2-125 和1.0001 × 2-125 這兩個數(分別對應於十進制的 2.6448623 × 10-38 和 2.4979255 × 10-38)。顯然,他們都是普通的浮點數(指數爲 -125,大於容許的最小值 -126;尾數更沒問題),按照 IEEE 754 能夠分別保存爲00000001000100000000000000000000(0x1100000)和 00000001000010000000000000000000(0x1080000)。
如今咱們看看這兩個浮點數的差值。不可貴出,該差值爲 0.0001 × 2-125,表達爲規範浮點數則爲 1.0 × 2-129。問題在於其指數大於容許的最小指數值,因此沒法保存爲規範浮點數。最終,只能近似爲零(Flush to Zero)。這中特殊狀況意味着下面原本十分可靠的代碼也可能出現問題:
if (x != y) {
z = 1 / (x -y);
}
正如咱們精心選擇的兩個浮點數展示的問題同樣,即便 x 不等於 y,x 和 y 的差值仍然可能絕對值太小,而近似爲零,致使除以0 的狀況發生。
爲了解決此類問題,IEEE 標準中引入了非規範(Denormalized)浮點數。規定當浮點數的指數爲容許的最小指數值,即 emin 時,尾數沒必要是規範化的。好比上面例子中的差值能夠表達爲非規範的浮點數 0.001 × 2-126,其中指數 -126 等於 emin。注意,這裏規定的是"沒必要",這也就意味着"能夠"。當浮點數實際的指數爲 emin,且指數域也爲 emin 時,該浮點數還是規範的,也就是說,保存時隱含着一個隱藏的尾數位。爲了保存非規範浮點數,IEEE 標準採用了相似處理特殊值零時所採用的辦法,即用特殊的指數域值 emin - 1 加以標記,固然,此時的尾數域不能爲零。這樣,例子中的差值能夠保存爲00000000000100000000000000000000(0x100000),沒有隱含的尾數位。
有了非規範浮點數,去掉了隱含的尾數位的制約,能夠保存絕對值更小的浮點數。並且,也因爲再也不受到隱含尾數域的制約,上述關於極小差值的問題也不存在了,由於全部能夠保存的浮點數之間的差值一樣能夠保存。
不少小數根本沒法在二進制計算機中精確表示(好比最簡單的 0.1)因爲浮點數尾數域的位數是有限的,爲此,浮點數的處理辦法是持續該過程直到由此獲得的尾數足以填滿尾數域,以後對多餘的位進行舍入。換句話說,除了咱們以前講到的精度問題以外,十進制到二進制的變換也並不能保證老是精確的,而只能是近似值。事實上,只有不多一部分十進制小數具備精確的二進制浮點數表達。再加上浮點數運算過程當中的偏差累積,結果是不少咱們看來很是簡單的十進制運算在計算機上卻每每出人意料。這就是最多見的浮點運算的"不許確"問題。
參見下面的 Java 示例:
System.out.print("34.6-34.0=" + (34.6f-34.0f));
這段代碼的輸出結果以下:
34.6-34.0=0.5999985
產生這個偏差的緣由是 34.6 沒法精確的表達爲相應的浮點數,而只能保存爲通過舍入的近似值。這個近似值與 34.0 之間的運算天然沒法產生精確的結果。
存儲格式的範圍和精度
格式 |
有效數字(二進制) |
最小正正規數 |
最大正數 |
有效數字(十進制) |
單精 |
24 |
1.175... 10-38 |
3.402... 10+38 |
6-9 |
雙精度 |
53 |
2.225... 10-308 |
1.797...10+308 |
15-17 |
雙精度擴展(SPARC) |
113 |
3.362... 10-4932 |
1.189...10+4932 |
33-36 |
雙精度擴展(x86) |
64 |
3.362... 10-4932 |
1.189...10+4932 |
18-21 |
值得注意的是,對於單精度數,因爲咱們只有 24 位的指數(其中一位隱藏),因此能夠表達的最大指數爲 224 - 1 = 16,777,215。特別的,16,777,216 是偶數,因此咱們能夠經過將它除以 2 並相應地調整指數來保存這個數,這樣 16,777,216 一樣能夠被精確的保存。相反,數值 16,777,217 則沒法被精確的保存。由此,咱們能夠看到單精度的浮點數能夠表達的十進制數值中,真正有效的數字不高於 8 位。事實上,對相對偏差的數值分析結果顯示有效的精度大約爲 7.22 位。參考下面的示例:
真值(true value) |
存儲值(stored value) |
16,777,215 |
1.6777215E7 |
16,777,216 |
1.6777216E7 |
16,777,217 |
1.6777216E7 |
16,777,218 |
1.6777218E7 |
16,777,219 |
1.677722E7 |
16,777,220 |
1.677722E7 |
16,777,221 |
1.677722E7 |
16,777,222 |
1.6777222E7 |
16,777,223 |
1.6777224E7 |
16,777,224 |
1.6777224E7 |
16,777,225 |
1.6777224E7 |
根據標準要求,沒法精確保存的值必須向最接近的可保存的值進行舍入。這有點像咱們熟悉的十進制的四捨五入,即不足一半則舍,一半以上(包括一半)則進。不過對於二進制浮點數而言,還多一條規矩,就是當須要舍入的值恰好是一半時,不是簡單地進,而是在先後兩個等距接近的可保存的值中,取其中最後一位有效數字爲零者。從上面的示例中能夠看出,奇數都被舍入爲偶數,且有舍有進。咱們能夠將這種舍入偏差理解爲"半位"的偏差。因此,爲了不 7.22 對不少人形成的困惑,有些文章常常以7.5 位來講明單精度浮點數的精度問題。
提示: 這裏採用的浮點數舍入規則有時被稱爲舍入到偶數(Round to Even)。相比簡單地逢一半則進的舍入規則,舍入到偶數有助於從某些角度減少計算中產生的舍入偏差累積問題。所以爲 IEEE 標準所採用。