浮点数运算原理详解
隨著你經(jīng)驗(yàn)的增長(zhǎng),你肯定 想去深入了解一些常見的東西的細(xì)節(jié),浮點(diǎn)數(shù)運(yùn)算就是其中之一。
1. 什么是浮點(diǎn)數(shù)?
在計(jì)算機(jī)系統(tǒng)的發(fā)展過程中,曾經(jīng)提出過多種方法表達(dá)實(shí)數(shù)。
【1】典型的比如相對(duì)于浮點(diǎn)數(shù)的定點(diǎn)數(shù)(Fixed Point Number)。在這種表達(dá)方式中,小數(shù)點(diǎn)固定的位于實(shí)數(shù)所有數(shù)字中間的某個(gè)位置。貨幣的表達(dá)就可以使用這種方式,比如 99.00 或者 00.99 可以用于表達(dá)具有四位精度(Precision),小數(shù)點(diǎn)后有兩位的貨幣值。由于小數(shù)點(diǎn)位置固定,所以可以直接用四位數(shù)值來(lái)表達(dá)相應(yīng)的數(shù)值。SQL 中的 NUMBER 數(shù)據(jù)類型就是利用定點(diǎn)數(shù)來(lái)定義的。
【2】還有一種提議的表達(dá)方式為有理數(shù)表達(dá)方式,即用兩個(gè)整數(shù)的比值來(lái)表達(dá)實(shí)數(shù)。
定點(diǎn)數(shù)表達(dá)法的缺點(diǎn)在于其形式過于僵硬,固定的小數(shù)點(diǎn)位置決定了固定位數(shù)的整數(shù)部分和小數(shù)部分,不利于同時(shí)表達(dá)特別大的數(shù)或者特別小的數(shù)。最終,絕大多數(shù)現(xiàn)代的計(jì)算機(jī)系統(tǒng)采納了所謂的浮點(diǎn)數(shù)表達(dá)方式。
【3】浮點(diǎn)數(shù)表達(dá)方式, 這種表達(dá)方式利用科學(xué)計(jì)數(shù)法來(lái)表達(dá)實(shí)數(shù),即用一個(gè)尾數(shù)(Mantissa ),一個(gè)基數(shù)(Base),一個(gè)指數(shù)(Exponent)以及一個(gè)表示正負(fù)的符號(hào)來(lái)表達(dá)實(shí)數(shù)。比如 123.45 用十進(jìn)制科學(xué)計(jì)數(shù)法可以表達(dá)為 1.2345 × 102 ,其中 1.2345 為尾數(shù),10 為基數(shù),2 為指數(shù)。浮點(diǎn)數(shù)利用指數(shù)達(dá)到了浮動(dòng)小數(shù)點(diǎn)的效果,從而可以靈活地表達(dá)更大范圍的實(shí)數(shù)。提示: 尾數(shù)有時(shí)也稱為有效數(shù)字(Significand)。尾數(shù)實(shí)際上是有效數(shù)字的非正式說法。
同樣的數(shù)值可以有多種浮點(diǎn)數(shù)表達(dá)方式,比如上面例子中的 123.45 可以表達(dá)為 12.345 × 101,0.12345 × 103 或者 1.2345 × 102。因?yàn)檫@種多樣性,有必要對(duì)其加以規(guī)范化以達(dá)到統(tǒng)一表達(dá)的目標(biāo)。規(guī)范的(Normalized)浮點(diǎn)數(shù)表達(dá)方式具有如下形式:
d.dd...d × βe?, (0 ≤ di?< β)
其中?d.dd...d 即尾數(shù),β 為基數(shù),e 為指數(shù)。尾數(shù)中數(shù)字的個(gè)數(shù)稱為精度,在本文中用 p(presion) 來(lái)表示。每個(gè)數(shù)字 d 介于 0 和基數(shù)β之間,包括 0。小數(shù)點(diǎn)左側(cè)的數(shù)字不為 0。
(1) 基于規(guī)范表達(dá)的浮點(diǎn)數(shù)對(duì)應(yīng)的具體值可由下面的表達(dá)式計(jì)算而得:(p是精度個(gè)數(shù))
±(d0 + d1β-1 + ... + dp-1β-(p-1))βe , (0 ≤ di < β)
對(duì)于十進(jìn)制的浮點(diǎn)數(shù),即基數(shù) β 等于 10 的浮點(diǎn)數(shù)而言,上面的表達(dá)式非常容易理解,也很直白。計(jì)算機(jī)內(nèi)部的數(shù)值表達(dá)是基于二進(jìn)制的。從上面的表達(dá)式,我們可以知道,二進(jìn)制數(shù)同樣可以有小數(shù)點(diǎn),也 同樣具有類似于十進(jìn)制的表達(dá)方式。只是此時(shí) β 等于 2,而每個(gè)數(shù)字 d 只能在 0 和 1 之間取值。
(2) 比如二進(jìn)制數(shù) 1001.101 相當(dāng)于:精度為7
1 × 2?3?+ 0 × 22?+ 0 × 21?+ 1 × 20?+ 1 × 2-1?+ 0 × 2-2?+ 1 × 2-3,對(duì)應(yīng)于十進(jìn)制的 9.625。
其規(guī)范浮點(diǎn)數(shù)表達(dá)為 1.001101 × 23。
(3) IEEE (美國(guó)電氣和電子工程師學(xué)會(huì))浮點(diǎn)數(shù)
計(jì)算機(jī)中是用有限的連續(xù)字節(jié)保存浮點(diǎn)數(shù)的。
IEEE定義了多種浮點(diǎn)格式,但最常見的是三種類型:單精度、雙精度、擴(kuò)展雙精度,分別適用于不同的計(jì)算要求。一般而言,單精度適合一般計(jì)算,雙精度適合科學(xué)計(jì)算,擴(kuò)展雙精度適合高精度計(jì)算。一個(gè)遵循IEEE 754標(biāo)準(zhǔn)的系統(tǒng)必須支持單精度類型(強(qiáng)制類型)、最好也支持雙精度類型(推薦類型),至于擴(kuò)展雙精度類型可以隨意。單精度(Single Precision)浮點(diǎn)數(shù)是32位(即4字節(jié))的,雙精度(Double Precision)浮點(diǎn)數(shù)是64位(即8字節(jié))的。
保存這些浮點(diǎn)數(shù)當(dāng)然必須有特定的格式,Java 平臺(tái)上的浮點(diǎn)數(shù)類型 float 和 double 采納了 IEEE 754 標(biāo)準(zhǔn)中所定義的單精度 32 位浮點(diǎn)數(shù)和雙精度 64 位浮點(diǎn)數(shù)的格式。注意:?Java 平臺(tái)還支持該標(biāo)準(zhǔn)定義的兩種擴(kuò)展格式,即 float-extended-exponent 和 double-extended-exponent 擴(kuò)展格式。這里將不作介紹,有興趣的讀者可以參考相應(yīng)的參考資料。
在 IEEE 標(biāo)準(zhǔn)中,浮點(diǎn)數(shù)是將特定長(zhǎng)度的連續(xù)字節(jié)的所有二進(jìn)制位分割為特定寬度的符號(hào)域,指數(shù)域和尾數(shù)域三個(gè)域,其中保存的值分別用于表示給定二進(jìn)制浮點(diǎn)數(shù)中的符號(hào),指數(shù)和尾數(shù)。這樣,通過尾數(shù)和可以調(diào)節(jié)的指數(shù)(所以稱為"浮點(diǎn)")就可以表達(dá)給定的數(shù)值了。
具體的格式參見下面的表格:
需要特別注意的是,擴(kuò)展雙精度類型沒有隱含位,因此它的有效位數(shù)與尾數(shù)位數(shù)一致,而單精度類型和雙精度類型均有一個(gè)隱含位,因此它的有效位數(shù)比位數(shù)位數(shù)多一個(gè)。
?
IEEE754標(biāo)準(zhǔn)規(guī)定一個(gè)實(shí)數(shù)V可以用: V=(-1)s×M×2^E的形式表示,說明如下:
(1)符號(hào)s(sign)決定實(shí)數(shù)是正數(shù)(s=0)還是負(fù)數(shù)(s=1),對(duì)數(shù)值0的符號(hào)位特殊處理。
(2)有效數(shù)字M是二進(jìn)制小數(shù),M的取值范圍在1≤M<2或0≤M<1。
(3)指數(shù)E(exponent)是2的冪,它的作用是對(duì)浮點(diǎn)數(shù)加權(quán)。
? 為了強(qiáng)制定義一些特殊值,IEEE標(biāo)準(zhǔn)通過指數(shù)將表示空間劃分成了三大塊:
【1】最小值指數(shù)(所有位全置0)用于定義0和弱規(guī)范數(shù)
【2】最大指數(shù)(所有位全值1)用于定義±∞和NaN(Not a Number)
【3】其他指數(shù)用于表示常規(guī)的數(shù)。
這樣一來(lái),最大(指絕對(duì)值)常規(guī)數(shù)的指數(shù)不是全1的,最小常規(guī)數(shù)的指數(shù)也不是0,而是1。
S:符號(hào)位, Exponent:指數(shù)域 Fraction:尾數(shù)域
注意:尾數(shù)有時(shí)也稱為有效數(shù)字(Significand),
一般如1.001001*2EValue,即一個(gè)尾數(shù)(Mantissa ),一個(gè)基數(shù)(底數(shù)Base),一個(gè)指數(shù)Evalue表示
即: M * BE?=?尾數(shù) * 底數(shù)指數(shù)
通常情況,IEEE標(biāo)準(zhǔn)寫法,尾數(shù)的1,省略,Fraction= 0.001001,因?yàn)闃?biāo)準(zhǔn)寫法,前面的1總是省略Fraction = 尾數(shù) - 1?;(IEEE規(guī)定小數(shù)點(diǎn)左側(cè)的 1 是隱藏的)
如果指數(shù)值:加上相應(yīng)的浮點(diǎn)數(shù)偏執(zhí)后的值:即?Exponent = EValue + Bias。
所以上述的值: X = (-1)S? X ( 1 + Fraction)?(Exponent - Bias), 也就不足為奇了
?
在上面的圖例中:
?、佟 〉谝粋€(gè)域:為符號(hào)域。其中?0 表示數(shù)值為正數(shù),而 1 則表示負(fù)數(shù)。
?、凇 〉诙€(gè)域為指數(shù)域,對(duì)應(yīng)于我們之前介紹的二進(jìn)制科學(xué)計(jì)數(shù)法中的指數(shù)部分。
指數(shù)閾:通常使用移碼表示:
(移碼和補(bǔ)碼只有符號(hào)位相反,其余都一樣。對(duì)于正數(shù)而言,原碼、反碼和補(bǔ)碼都一樣;對(duì)于負(fù)數(shù)而言,補(bǔ)碼就是其絕對(duì)值的原碼全部取反,然后加1(不包括符號(hào)位))。
其中單精度數(shù)為 8 位,雙精度數(shù)為 11 位。以單精度數(shù)為例,8 位的指數(shù)為可以表達(dá) 0 到 255 之間的 255 個(gè)指數(shù)值。
但是,指數(shù)可以為正數(shù),也可以為負(fù)數(shù)。為了處理負(fù)指數(shù)的情況,實(shí)際的指數(shù)值按要求需要加上一個(gè)偏差(Bias)值作為保存在指數(shù)域中的值,單精度數(shù)的偏差值為 127(0-111 1111)(8位),而雙精度數(shù)的偏差值為 1023(0-1 1111 1111)(10位)。比如,單精度的實(shí)際指數(shù)值 0?在指數(shù)域中將保存為 127;而保存在指數(shù)域中的 64 則表示實(shí)際的指數(shù)值 -63。偏差的引入使得對(duì)于單精度數(shù),實(shí)際可以表達(dá)的指數(shù)值的范圍就變成 -127 到 128 之間(包含兩端)[-127, 128]。
我們不久還將看到:
實(shí)際的指數(shù)值 -127(保存為 全 0),即: 首先-127原碼1-111 1111,的補(bǔ)碼1-000 0001,然后加上單精度偏執(zhí): 0-111 111 ,即結(jié)果:0-000 0000,全0. 所以0-000 0000 指數(shù)位表示:-127,即e-127
以及 +128(保存為全 1), 即:首先+128原碼‘1’-000 0000,的補(bǔ)碼, ‘1’-000 0000,然后加上單精度偏執(zhí):0-111 111 ,, 即結(jié)果:‘1’-111 1111,全1。? 即全1 指數(shù)位表示:+128,即e+128
這些特殊值,保留用作特殊值的處理。這樣,實(shí)際可以表達(dá)的有效指數(shù)范圍就在 -127 和 127 之間。在本文中,最小指數(shù)和最大指數(shù)分別用 emin 和 emax 來(lái)表達(dá)。
?
計(jì)算機(jī)中的符號(hào)數(shù)有三種表示方法,即原碼、反碼和補(bǔ)碼。
如補(bǔ)碼的求取:
① 正數(shù)(符號(hào)位為0的數(shù))補(bǔ)碼與原碼相同.
② 負(fù)數(shù)(符號(hào)位為1的數(shù))變?yōu)檠a(bǔ)碼時(shí)符號(hào)位不變,其余各項(xiàng)取反,最后在末尾+1;即求負(fù)數(shù)的反碼不包括符號(hào)位。
例如:正數(shù) 原碼01100110,補(bǔ)碼為:01100110
負(fù)數(shù) 原碼11100110,先變反碼:10011001,再加1變?yōu)檠a(bǔ)碼:10011010
計(jì)算機(jī)中的符號(hào)數(shù)有三種表示方法,即原碼、反碼和補(bǔ)碼。三種表示方法均有符號(hào)位和數(shù)值位兩部分,符號(hào)位都是用0表示“正”,用1表示“負(fù)”,而數(shù)值位,三種表示方法各不相同。
在計(jì)算機(jī)系統(tǒng)中,數(shù)值一律用補(bǔ)碼來(lái)表示和存儲(chǔ)。原因在于:①使用補(bǔ)碼,可以將符號(hào)位和數(shù)值域統(tǒng)一處理;②同時(shí),加法和減法也可以統(tǒng)一處理。此外,③補(bǔ)碼與原碼相互轉(zhuǎn)換,其運(yùn)算過程是相同的,不需要額外的硬件電路。
特性
① 一個(gè)負(fù)整數(shù)(或原碼)與其補(bǔ)數(shù)(或補(bǔ)碼)相加,和為模。eg:原碼11100110, 補(bǔ)碼:10011010 和:
?、凇 ?duì)一個(gè)整數(shù)的補(bǔ)碼再求補(bǔ)碼,等于該整數(shù)自身。
③ 補(bǔ)碼的正零與負(fù)零表示方法相同。即 0-0000000, 1-0000000取反加1, 0-0000000
?
?、邸 D例中的第三個(gè)域?yàn)槲矓?shù)域,其中單精度數(shù)為 23 位長(zhǎng),雙精度數(shù)為 52 位長(zhǎng)。除了我們將要講到的某些特殊值外,IEEE 標(biāo)準(zhǔn)要求浮點(diǎn)數(shù)必須是規(guī)范的。這意味著尾數(shù)的小數(shù)點(diǎn)左側(cè)必須為 1,因此我們?cè)诒4嫖矓?shù)的時(shí)候,可以省略小數(shù)點(diǎn)前面這個(gè) 1,從而騰出一個(gè)二進(jìn)制位來(lái)保存更多的尾數(shù)。這樣我們實(shí)際上用 23 位長(zhǎng)的尾數(shù)域表達(dá)了 24 位的尾數(shù)。比如對(duì)于單精度數(shù)而言,二進(jìn)制的 1001.101(對(duì)應(yīng)于十進(jìn)制的 9.625)可以表達(dá)為 1.001101 × 23,所以實(shí)際保存在尾數(shù)域中的值為 00110100000000000000000,即去掉小數(shù)點(diǎn)左側(cè)的 1,并用 0 在右側(cè)補(bǔ)齊。
? 根據(jù)IEEE(美國(guó)電氣和電子工程師學(xué)會(huì))754標(biāo)準(zhǔn)要求,無(wú)法精確保存的值必須向最接近的可保存的值進(jìn)行舍入。這有點(diǎn)像我們熟悉的十進(jìn)制的四舍五入,即不足一半則舍,一半以上(包括一半)則進(jìn)。不過對(duì)于二進(jìn)制浮 點(diǎn)數(shù)而言,還多一條規(guī)矩,就是當(dāng)需要舍入的值剛好是一半時(shí),不是簡(jiǎn)單地進(jìn),而是在前后兩個(gè)等距接近的可保存的值中,取其中最后一位有效數(shù)字為零者。從上面 的示例中可以看出,奇數(shù)都被舍入為偶數(shù),且有舍有進(jìn)。我們可以將這種舍入誤差理解為"半位"的誤差。所以,為了避免 7.22 對(duì)很多人造成的困惑,有些文章經(jīng)常以 7.5 位來(lái)說明單精度浮點(diǎn)數(shù)的精度問題。
據(jù)以上分析,IEEE 754標(biāo)準(zhǔn)中定義浮點(diǎn)數(shù)的表示范圍為:
單精度浮點(diǎn)數(shù) 二進(jìn)制:± (2-2^-23) × 2127 對(duì)應(yīng)十進(jìn)制: ~ ± 10^38.53
雙精度浮點(diǎn)數(shù) ? 二進(jìn)制:± (2-2^-52) × 21023
浮點(diǎn)數(shù)的表示有一定的范圍,超出范圍時(shí)會(huì)產(chǎn)生溢出(Flow),一般稱大于絕對(duì)值最大的數(shù)據(jù)為上溢(Overflow),小于絕對(duì)值最小的數(shù)據(jù)為下溢(Underflow)。
?
2.?浮點(diǎn)數(shù)的表示約定
單精度浮點(diǎn)數(shù)和雙精度浮點(diǎn)數(shù)都是用IEEE 754標(biāo)準(zhǔn)定義的,其中有一些特殊約定,例如:
(1) 當(dāng)P=0,M=0時(shí),表示0。
(2) 當(dāng)P=255,M=0時(shí),表示無(wú)窮大,用符號(hào)位來(lái)確定是正無(wú)窮大還是負(fù)無(wú)窮大。
(3) 當(dāng)P=255,M≠0時(shí),表示NaN(Not a Number,不是一個(gè)數(shù))。
?
3.?特殊值
通過前面的介紹,你應(yīng)該已經(jīng)了解的浮點(diǎn)數(shù)的基本知識(shí),這些知識(shí)對(duì)于一個(gè)不接觸浮點(diǎn)數(shù)應(yīng)用的人應(yīng)該足夠了。不過,如果你興趣正濃,或者面對(duì)著一個(gè)棘手的浮點(diǎn)數(shù)應(yīng)用,可以通過本節(jié)了解到關(guān)于浮點(diǎn)數(shù)的一些值得注意的特殊之處。
我們已經(jīng)知道,單精度浮點(diǎn)數(shù)指數(shù)域?qū)嶋H可以表達(dá)的指數(shù)值的范圍為 -127 到 128 之間(包含兩端)。其中,值 -127(保存為全0)以及 +128(保存為全1)保留用作特殊值的處理。本節(jié)將詳細(xì) IEEE 標(biāo)準(zhǔn)中所定義的這些特殊值。
浮點(diǎn)數(shù)中的特殊值主要用于特殊情況或者錯(cuò)誤的處理。比如在程序?qū)σ粋€(gè)負(fù)數(shù)進(jìn)行開平方時(shí),一個(gè)特殊的返回值將用于標(biāo)記這種錯(cuò)誤,該值為 NaN(Not a Number)。沒有這樣的特殊值,對(duì)于此類錯(cuò)誤只能粗暴地終止計(jì)算。除了 NaN 之外,IEEE 標(biāo)準(zhǔn)還定義了 ±0,±∞ 以及非規(guī)范化數(shù)(Denormalized Number)。
對(duì)于單精度浮點(diǎn)數(shù),所有這些特殊值都由保留的特殊指數(shù)值 -127 和 128 來(lái)編碼。如果我們分別用?emin?和?emax?來(lái)表達(dá)其它常規(guī)指數(shù)值范圍的邊界,即 -126 和 127,則保留的特殊指數(shù)值可以分別表達(dá)為?emin?- 1 和?emax?+ 1; ?;谶@個(gè)表達(dá)方式,IEEE 標(biāo)準(zhǔn)的特殊值如下所示:
其中?f?表示尾數(shù)中的小數(shù)點(diǎn)右側(cè)的(Fraction)部分,即標(biāo)準(zhǔn)記法中的有效部分-1。
第一行即我們之前介紹的普通的規(guī)范化浮點(diǎn)數(shù)。隨后我們將分別對(duì)余下的特殊值加以介紹。
第2,3,4,5行,是特殊值。
(1)NaN
NaN 用于處理計(jì)算中出現(xiàn)的錯(cuò)誤情況,比如 0.0 除以 0.0 或者求負(fù)數(shù)的平方根。
由上面的表中可以看出,對(duì)于單精度浮點(diǎn)數(shù),NaN 表示為指數(shù)為?emax?+ 1 =?128(指數(shù)域全為 1),且尾數(shù)域不等于零的浮點(diǎn)數(shù)。IEEE 標(biāo)準(zhǔn)沒有要求具體的尾數(shù)域,所以?NaN 實(shí)際上不是一個(gè),而是一族。
不同的實(shí)現(xiàn)可以自由選擇尾數(shù)域的值來(lái)表達(dá) NaN,比如 Java 中的常量 Float.NaN 的浮點(diǎn)數(shù)可能表達(dá)為 0-11111111-10000000000000000000000,其中尾數(shù)域的第一位為 1,其余均為 0(不計(jì)隱藏的一位),但這取決系統(tǒng)的硬件架構(gòu)。Java 中甚至允許程序員自己構(gòu)造具有特定位模式的 NaN 值(通過 Float.intBitsToFloat() 方法)。比如,程序員可以利用這種定制的 NaN 值中的特定位模式來(lái)表達(dá)某些診斷信息。定制的 NaN 值,可以通過 Float.isNaN() 方法判定其為 NaN,但是它和 Float.NaN 常量卻不相等。
實(shí)際上,所有的 NaN 值都是無(wú)序的。數(shù)值比較操作符 <,<=,> 和 >= 在任一操作數(shù)為 NaN 時(shí)均返回 false。等于操作符 == 在任一操作數(shù)為 NaN 時(shí)均返回 false,即使是兩個(gè)具有相同位模式的 NaN 也一樣。而操作符 != 則當(dāng)任一操作數(shù)為 NaN 時(shí)返回 true。
這個(gè)規(guī)則的一個(gè)有趣的結(jié)果是 x!=x 當(dāng) x 為 NaN 時(shí)竟然為真。
此外,任何有 NaN 作為操作數(shù)的操作也將產(chǎn)生 NaN。用特殊的 NaN 來(lái)表達(dá)上述運(yùn)算錯(cuò)誤的意義在于避免了因這些錯(cuò)誤而導(dǎo)致運(yùn)算的不必要的終止。比如,如果一個(gè)被循環(huán)調(diào)用的浮點(diǎn)運(yùn)算方法,可能由于輸入的參數(shù)問題而導(dǎo)致發(fā)生這些錯(cuò)誤,NaN 使得 即使某次循環(huán)發(fā)生了這樣的錯(cuò)誤,也可以簡(jiǎn)單地繼續(xù)執(zhí)行循環(huán)以進(jìn)行那些沒有錯(cuò)誤的運(yùn)算。你可能想到,既然 Java 有異常處理機(jī)制,也許可以通過捕獲并忽略異常達(dá)到相同的效果。但是,要知道,IEEE 標(biāo)準(zhǔn)不是僅僅為 Java 而制定的,各種語(yǔ)言處理異常的機(jī)制不盡相同,這將使得代碼的遷移變得更加困難。何況,不是所有語(yǔ)言都有類似的異常或者信號(hào)(Signal)處理機(jī)制。
(2)無(wú)窮
和 NaN 一樣,特殊值無(wú)窮(Infinity)的指數(shù)部分同樣為 emax + 1 = 128,不過無(wú)窮的尾數(shù)域必須為零。無(wú)窮用于表達(dá)計(jì)算中產(chǎn)生的上溢(Overflow)問題。比如兩個(gè)極大的數(shù)相乘時(shí),盡管兩個(gè)操作數(shù)本身可以用保存為浮點(diǎn)數(shù),但其結(jié)果可能大到無(wú)法保存為浮點(diǎn)數(shù),而必須進(jìn)行舍入。根據(jù) IEEE 標(biāo)準(zhǔn),此時(shí)不是將結(jié)果舍入為可以保存的最大的浮點(diǎn)數(shù)(因?yàn)檫@個(gè)數(shù)可能離實(shí)際的結(jié)果相差太遠(yuǎn)而毫無(wú)意義),而是將其舍入為無(wú)窮。對(duì)于負(fù)數(shù)結(jié)果也是如此,只不過此時(shí)舍入為負(fù)無(wú)窮,也就是說符號(hào)域?yàn)?1 的無(wú)窮。有了 NaN 的經(jīng)驗(yàn)我們不難理解,特殊值無(wú)窮使得計(jì)算中發(fā)生的上溢錯(cuò)誤不必以終止運(yùn)算為結(jié)果。
無(wú)窮和除 NaN 以外的其它浮點(diǎn)數(shù)一樣是有序的,從小到大依次為負(fù)無(wú)窮,負(fù)的有窮非零值,正負(fù)零(隨后介紹),正的有窮非零值以及正無(wú)窮。除 NaN 以外的任何非零值除以零,結(jié)果都將是無(wú)窮,而符號(hào)則由作為除數(shù)的零的符號(hào)決定。
回顧我們對(duì) NaN 的介紹,當(dāng)零除以零時(shí)得到的結(jié)果不是無(wú)窮而是 NaN 。原因不難理解,當(dāng)除數(shù)和被除數(shù)都逼近于零時(shí),其商可能為任何值,所以 IEEE 標(biāo)準(zhǔn)決定此時(shí)用 NaN 作為商比較合適。
(3)有符號(hào)的零
因?yàn)?IEEE 標(biāo)準(zhǔn)的浮點(diǎn)數(shù)格式中,小數(shù)點(diǎn)左側(cè)的 1 是隱藏的,而零顯然需要尾數(shù)必須是零。所以,零也就無(wú)法直接用這種格式表達(dá)而只能特殊處理。實(shí)際上,零保存為尾數(shù)域?yàn)槿珵?0,指數(shù)域?yàn)?emin - 1 = -127,也就是說指數(shù)域也全為 0??紤]到符號(hào)域的作用,所以存在著兩個(gè)零,即 +0 和 -0。不同于正負(fù)無(wú)窮之間是有序的,IEEE 標(biāo)準(zhǔn)規(guī)定正負(fù)零是相等的。
零有正負(fù)之分,的確非常容易讓人困惑。這一點(diǎn)是基于數(shù)值分析的多種考慮,經(jīng)利弊權(quán)衡后形成的結(jié)果。有符號(hào)的零可以避免運(yùn)算中,特別是涉及無(wú)窮的運(yùn)算中,符號(hào)信息的丟失。舉例而言,如果零無(wú)符號(hào),則等式 1/(1/x) = x 當(dāng)x = ±∞ 時(shí)不再成立。原因是如果零無(wú)符號(hào),1 和正負(fù)無(wú)窮的比值為同一個(gè)零,然后 1 與 0 的比值為正無(wú)窮,符號(hào)沒有了。解決這個(gè)問題,除非無(wú)窮也沒有符號(hào)。但是無(wú)窮的符號(hào)表達(dá)了上溢發(fā)生在數(shù)軸的哪一側(cè),這個(gè)信息顯然是不能不要的。零有符號(hào)也造成了其它問題,比如當(dāng) x=y 時(shí),等式1/x = 1/y 在 x 和 y 分別為 +0 和 -0 時(shí),兩端分別為正無(wú)窮和負(fù)無(wú)窮而不再成立。當(dāng)然,解決這個(gè)問題的另一個(gè)思路是和無(wú)窮一樣,規(guī)定零也是有序的。但是,如果零是有序的,則即使 if (x==0) 這樣簡(jiǎn)單的判斷也由于 x 可能是 ±0 而變得不確定了。兩害取其輕者,零還是無(wú)序的好。
(4)非規(guī)范化數(shù)
我們來(lái)考察浮點(diǎn)數(shù)的一個(gè)特殊情況。選擇兩個(gè)絕對(duì)值極小的浮點(diǎn)數(shù),以單精度的二進(jìn)制浮點(diǎn)數(shù)為例,比如 1.001 × 2-125 和 1.0001 × 2-125 這兩個(gè)數(shù)(分別對(duì)應(yīng)于十進(jìn)制的 2.6448623 × 10-38 和 2.4979255 × 10-38)。顯然,他們都是普通的浮點(diǎn)數(shù)(指數(shù)為 -125,大于允許的最小值 -126;尾數(shù)更沒問題),按照 IEEE 754 可以分別保存為 00000001000100000000000000000000(0x1100000)和 00000001000010000000000000000000(0x1080000)。
現(xiàn)在我們看看這兩個(gè)浮點(diǎn)數(shù)的差值。不難得出,該差值為 0.0001 × 2-125,表達(dá)為規(guī)范浮點(diǎn)數(shù)則為 1.0 × 2-129。問題在于其指數(shù)大于允許的最小指數(shù)值,所以無(wú)法保存為規(guī)范浮點(diǎn)數(shù)。最終,只能近似為零(Flush to Zero)。這中特殊情況意味著下面本來(lái)十分可靠的代碼也可能出現(xiàn)問題:
if (x != y) {z = 1 / (x -y);}
正如我們精心選擇的兩個(gè)浮點(diǎn)數(shù)展現(xiàn)的問題一樣,即使?x 不等于 y,x 和 y 的差值仍然可能絕對(duì)值過小,而近似為零,導(dǎo)致除以 0 的情況發(fā)生。
為了解決此類問題,IEEE 標(biāo)準(zhǔn)中引入了非規(guī)范(Denormalized)浮點(diǎn)數(shù)。規(guī)定當(dāng)浮點(diǎn)數(shù)的指數(shù)為允許的最小指數(shù)值,即 emin 時(shí),尾數(shù)不必是規(guī)范化的。比如上面例子中的差值可以表達(dá)為非規(guī)范的浮點(diǎn)數(shù) 0.001 × 2-126,其中指數(shù) -126 等于 emin。注意,這里規(guī)定的是"不必",這也就意味著"可以"。當(dāng)浮點(diǎn)數(shù)實(shí)際的指數(shù)為 emin,且指數(shù)域也為 emin 時(shí),該浮點(diǎn)數(shù)仍是規(guī)范的,也就是說,保存時(shí)隱含著一個(gè)隱藏的尾數(shù)位。為了保存非規(guī)范浮點(diǎn)數(shù),IEEE 標(biāo)準(zhǔn)采用了類似處理特殊值零時(shí)所采用的辦法,即用特殊的指數(shù)域值 emin - 1?加以標(biāo)記,當(dāng)然,此時(shí)的尾數(shù)域不能為零。這樣,例子中的差值可以保存為 00000000000100000000000000000000(0x100000),沒有隱含的尾數(shù)位。
有了非規(guī)范浮點(diǎn)數(shù),去掉了隱含的尾數(shù)位的制約,可以保存絕對(duì)值更小的浮點(diǎn)數(shù)。而且,也由于不再受到隱含尾數(shù)域的制約,上述關(guān)于極小差值的問題也不存在了,因?yàn)樗锌梢员4娴母↑c(diǎn)數(shù)之間的差值同樣可以保存。
4.?范圍和精度
很多小數(shù)根本無(wú)法在二進(jìn)制計(jì)算機(jī)中精確表示(比如最簡(jiǎn)單的 0.1)由于浮點(diǎn)數(shù)尾數(shù)域的位數(shù)是有限的,為此,浮點(diǎn)數(shù)的處理辦法是持續(xù)該過程直到由此得到的尾數(shù)足以填滿尾數(shù)域,之后對(duì)多余的位進(jìn)行舍入。
換句話說,除了我們之前講到的精度問題之外,十進(jìn)制到二進(jìn)制的變換也并不能保證總是精確的,而只能是近似值。
事實(shí)上,只有很少一部分十進(jìn)制小數(shù)具有精確的二進(jìn)制浮點(diǎn)數(shù)表達(dá)。再加上浮點(diǎn)數(shù)運(yùn)算過程中的誤差累積,結(jié)果是很多我們看來(lái)非常簡(jiǎn)單的十進(jìn)制運(yùn)算在計(jì)算機(jī)上卻往往出人意料。這就是最常見的浮點(diǎn)運(yùn)算的"不準(zhǔn)確"問題。
參見下面的 Java 示例:
System.out.print("34.6-34.0=" + (34.6f-34.0f));
這段代碼的輸出結(jié)果如下:
34.6-34.0=0.5999985
產(chǎn)生這個(gè)誤差的原因是?34.6 無(wú)法精確的表達(dá)為相應(yīng)的浮點(diǎn)數(shù),而只能保存為經(jīng)過舍入的近似值。這個(gè)近似值與 34.0 之間的運(yùn)算自然無(wú)法產(chǎn)生精確的結(jié)果。
存儲(chǔ)格式的范圍和精度如下表所示:
5. 舍入
值得注意的是,對(duì)于單精度數(shù),由于我們只有?24 位的尾數(shù)(其中一位隱藏),所以可以表達(dá)的最大指數(shù)為 224?- 1 = 16,777,215。
特別的,16,777,216 是偶數(shù),所以我們可以通過將它除以 2 并相應(yīng)地調(diào)整指數(shù)來(lái)保存這個(gè)數(shù),這樣 16,777,216 同樣可以被精確的保存。相反,數(shù)值 16,777,217 則無(wú)法被精確的保存。由此,我們可以看到單精度的浮點(diǎn)數(shù)可以表達(dá)的十進(jìn)制數(shù)值中,真正有效的數(shù)字不高于 8 位。
事實(shí)上,對(duì)相對(duì)誤差的數(shù)值分析結(jié)果顯示有效的精度大約為 7.22 位。
實(shí)例如下所示:
根 據(jù)標(biāo)準(zhǔn)要求,無(wú)法精確保存的值必須向最接近的可保存的值進(jìn)行舍入。這有點(diǎn)像我們熟悉的十進(jìn)制的四舍五入,即不足一半則舍,一半以上(包括一半)則進(jìn)。不過 對(duì)于二進(jìn)制浮點(diǎn)數(shù)而言,還多一條規(guī)矩,就是當(dāng)需要舍入的值剛好是一半時(shí),不是簡(jiǎn)單地進(jìn),而是在前后兩個(gè)等距接近的可保存的值中,取其中最后一位有效數(shù)字為 零者。從上面的示例中可以看出,奇數(shù)都被舍入為偶數(shù),且有舍有進(jìn)。我們可以將這種舍入誤差理解為"半位"的誤差。所以,為了避免 7.22 對(duì)很多人造成的困惑,有些文章經(jīng)常以 7.5 位來(lái)說明單精度浮點(diǎn)數(shù)的精度問題。
提示:?這里采用的浮點(diǎn)數(shù)舍入規(guī)則有時(shí)被稱為舍入到偶數(shù)(Round to Even)。相比簡(jiǎn)單地逢一半則進(jìn)的舍入規(guī)則,舍入到偶數(shù)有助于從某些角度減小計(jì)算中產(chǎn)生的舍入誤差累積問題。因此為 IEEE 標(biāo)準(zhǔn)所采用。
轉(zhuǎn)載自:http://blog.csdn.net/tercel_zhang/article/details/52537726
總結(jié)
- 上一篇: 女人微信个性签名唯美
- 下一篇: NDK JNI方式读写Android系