在計算機系統的發展過程當中,曾經提出過多種方法表達實數。典型的好比相對於浮點數的定點數(Fixed Point Number)。在這種表達方式中,小數點固定的位於實數全部數字中間的某個位置。貨幣的表達就可使用這種方式,好比 99.00 或者 00.99 能夠用於表達具備四位精度(Precision),小數點後有兩位的貨幣值。因爲小數點位置固定,因此能夠直接用四位數值來表達相應的數值。SQL 中的 NUMBER 數據類型就是利用定點數來定義的。還有一種提議的表達方式爲有理數表達方式,即用兩個整數的比值來表達實數。java 定點數表達法的缺點在於其形式過於僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利於同時表達特別大的數或者特別小的數。最終,絕大多數現代的計算機系統採納了所謂的浮點數表達方式。程序員
它一般被表示成:算法 N = M* RE架構
Ms是尾數的符號位,即浮點數的符號位,安排在最高一位;
2. IEEE 浮點數在 IEEE 標準中,浮點數是將特定長度的連續字節的全部二進制位分割爲特定寬度的符號域,指數域和尾數域三個域,其中保存的值分別用於表示給定二進制浮點數中的符號,指數和尾數。spa 這樣,經過尾數和能夠調節的指數(因此稱爲"浮點")就能夠表達給定的數值了。具體的格式參見下面的圖例:.net 在上面的圖例中,第一個域爲符號域。其中 0 表示數值爲正數,而 1 則表示負數。orm 第二個域爲指數域,對應於咱們以前介紹的二進制科學計數法中的指數部分。其中單精度數爲 8 位,雙精度數爲 11 位。以單精度數爲例,8 位的指數爲能夠表達 0 到 255 之間的 255 個指數值。可是,指數能夠爲正數,也能夠爲負數。爲了處理負指數的狀況,實際的指數值按要求須要加上一個誤差(Bias)值做爲保存在指數域中的值,單精度數的誤差值爲 127,而雙精度數的誤差值爲 1023。好比,單精度的實際指數值 0 在指數域中將保存爲 127;而保存在指數域中的 64 則表示實際的指數值 -63。 誤差的引入使得對於單精度數,實際能夠表達的指數值的範圍就變成 -127 到 128 之間(包含兩端)。咱們不久還將看到,實際的指數值 -127(保存爲 全 0)以及 +128(保存爲全 1)保留用做特殊值的處理。這樣,實際能夠表達的有效指數範圍就在 -127 和 127 之間。在本文中,最小指數和最大指數分別用 emin 和 emax 來表達。blog 圖例中的第三個域爲尾數域,其中單精度數爲 23 位長,雙精度數爲 52 位長。除了咱們將要講到的某些特殊值外,IEEE 標準要求浮點數必須是規範的。這意味着尾數的小數點左側必須爲 1,所以咱們在保存尾數的時候,能夠省略小數點前面這個 1,從而騰出一個二進制位來保存更多的尾數。這樣咱們實際上用 23 位長的尾數域表達了 24 位的尾數。好比對於單精度數而言,二進制的 1001.101(對應於十進制的 9.625)能夠表達爲 1.001101 × 23,因此實際保存在尾數域中的值爲 00110100000000000000000,即去掉小數點左側的 1,並用 0 在右側補齊。 值得注意的是,對於單精度數,因爲咱們只有 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 位來講明單精度浮點數的精度問題。
3. 實數和浮點數之間的變換如今咱們已經明白了浮點數的 IEEE 表達方式。咱們來作些實數和浮點數之間的變換練習以加深理解。在這些練習中,你還會發現一些圍繞浮點數運算的使人吃驚的事實。 首先咱們來看看事情簡單的一面,從浮點數變換到實數。理解了浮點數的格式,作這個練習並不難。假定咱們有一個 32 位的數據,用十六進制表示爲 0xC0B40000,而且咱們知道它其實是一個單精度的浮點數。爲了獲得該浮點數實際表達的實數,咱們首先將它變換爲二進制形式: C 0 B 4 0 0 0 0 1100 0000 1011 0100 0000 0000 0000 0000 接着按照浮點數的格式切分爲相應的域: 1 10000001 01101000000000000000000 符號域 1 意味着負數;指數域爲 129 意味着實際的指數爲 2 (減去誤差值 127);尾數域爲 01101 意味着實際的二進制尾數爲 1.01101 (加上隱含的小數點前面的 1)。因此,實際的實數爲: -1.01101 × 22-(20+ 2-2+ 2-32-5) × 22-5.625 從實數向浮點數變換稍微麻煩一點。假定咱們須要將實數 -9.625 表達爲單精度的浮點數格式。方法是首先將它用二進制浮點數表達,而後變換爲相應的浮點數格式。 首先,將小數點左側的整數部分變換爲其二進制形式,9 的二進制性形式爲 1001。處理小數部分的算法是將咱們的小數部分乘以基數 2,記錄乘積結果的整數部分,接着將結果的小數部分繼續乘以 2,並不斷繼續該過程: 0.625 × 2 = 1.25 1 0.25 × 2 = 0.5 0 0.5 × 2 = 1 1 0 當最後的結果爲零時,結束這個過程。這時右側的一列數字就是咱們所需的二進制小數部分,即 0.101。這樣,咱們就獲得了完整的二進制形式 1001.101。用規範浮點數表達爲 1.001101 × 23。 由於是負數,因此符號域爲 1。指數爲 3,因此指數域爲 3 + 127 = 130,即二進制的 10000010。尾數省略掉小數點左側的 1 以後爲 001101,右側用零補齊。最終結果爲: 1 10000010 00110100000000000000000 最後能夠將浮點數形式表示爲十六進制的數據以下: 1100 0001 0001 1010 0000 0000 0000 0000 C 1 1 A 0 0 0 0 最終結果爲 0xC11A0000。 很簡單?等等!你可能已經注意到了,在上面這個咱們有意選擇的示例中,不斷的將產生的小數部分乘以 2 的過程掩蓋了一個事實。該過程結束的標誌是小數部分乘以 2 的結果爲 1,不難想象,不少小數根本不能通過有限次這樣的過程而獲得結果(好比最簡單的 0.1)。咱們已經知道浮點數尾數域的位數是有限的,爲此,浮點數的處理辦法是持續該過程直到由此獲得的尾數足以填滿尾數域,以後對多餘的位進行舍入。換句話說,除了咱們以前講到的精度問題以外,十進制到二進制的變換也並不能保證老是精確的,而只能是近似值。事實上,只有不多一部分十進制小數具備精確的二進制浮點數表達。再加上浮點數運算過程當中的偏差累積,結果是不少咱們看來很是簡單的十進制運算在計算機上卻每每出人意料。這就是最多見的浮點運算的"不許確"問題。參見下面的 Java 示例: System.out.print("34.6-34.0=" + (34.6f-34.0f)); 這段代碼的輸出結果以下: 34.6-34.0=0.5999985 產生這個偏差的緣由是 34.6 沒法精確的表達爲相應的浮點數,而只能保存爲通過舍入的近似值。這個近似值與 34.0 之間的運算天然沒法產生精確的結果。 4. 特殊值經過前面的介紹,你應該已經瞭解的浮點數的基本知識,這些知識對於一個不接觸浮點數應用的人應該足夠了。不過,若是你興趣正濃,或者面對着一個棘手的浮點數應用,能夠經過本節瞭解到關於浮點數的一些值得注意的特殊之處。 咱們已經知道,指數域實際能夠表達的指數值的範圍爲 -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)部分。第一行即咱們以前介紹的普通的規範化浮點數。隨後咱們將分別對餘下的特殊值加以介紹。 4.1. NaNNaN 用於處理計算中出現的錯誤狀況,好比 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)處理機制。
4.2. 無窮和 NaN 同樣,特殊值無窮(Infinity)的指數部分一樣爲 emax + 1 = 128,不過無窮的尾數域必須爲零。無窮用於表達計算中產生的上溢(Overflow)問題。好比兩個極大的數相乘時,儘管兩個操做數自己能夠用保存爲浮點數,但其結果可能大到沒法保存爲浮點數,而必須進行舍入。根據 IEEE 標準,此時不是將結果舍入爲能夠保存的最大的浮點數(由於這個數可能離實際的結果相差太遠而毫無心義),而是將其舍入爲無窮。對於負數結果也是如此,只不過此時舍入爲負無窮,也就是說符號域爲 1 的無窮。有了 NaN 的經驗咱們不難理解,特殊值無窮使得計算中發生的上溢錯誤沒必要以終止運算爲結果。 無窮和除 NaN 之外的其它浮點數同樣是有序的,從小到大依次爲負無窮,負的有窮非零值,正負零(隨後介紹),正的有窮非零值以及正無窮。除 NaN 之外的任何非零值除以零,結果都將是無窮,而符號則由做爲除數的零的符號決定。 回顧咱們對 NaN 的介紹,當零除以零時獲得的結果不是無窮而是 NaN 。緣由不難理解,當除數和被除數都逼近於零時,其商可能爲任何值,因此 IEEE 標準決定此時用 NaN 做爲商比較合適。 4.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 而變得不肯定了。兩害取其輕者,零仍是無序的好。 4.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),沒有隱含的尾數位。 有了非規範浮點數,去掉了隱含的尾數位的制約,能夠保存絕對值更小的浮點數。並且,也因爲再也不受到隱含尾數域的制約,上述關於極小差值的問題也不存在了,由於全部能夠保存的浮點數之間的差值一樣能夠保存。
原文地址:http://blog.csdn.net/cppptr/article/details/573372(有修改) |