參考:
1.http://www.cnblogs.com/cloudseawang/archive/2007/02/06/641652.html
2.http://www.cnblogs.com/chenwu128/archive/2012/10/07/2714120.html
簡介:
本文主要介紹了定點數和浮點數的概念,定點數和浮點數的加減運算(比如34.6f-34.0f),最后介紹了浮點數的特殊值
I.定點數
所謂定點格式,即約定機器中所有數據的小數點位置是固定不變的。通常將定點數據表示成純小數或純整數,為了將數表示成純小數,通常把小數點固定在數值部分的最高位之前;而為了將數表示成純整數,則把小數點固定在數值部分的最后面,如下圖所示:
圖中所標示的小數點在機器中是不表示出來的,而是事先約定在固定的位置。對於一台計算機,一旦確定了小數點的位置,就不再改變。
假設用n位來表示一個定點數x=x0x1x2...x(n-1),其中x0用來表示數的符號位,通常放在最左位置,並用數值0和1分別表示正號和負號,其余位數表示它的量值。如果定點數x表示純整數,則小數點位於最低位x(n-1)的右邊,數值范圍是0<=|x|<=2^(n-1)-1,且,例如1111表示-7;如果定點數x表示純小數,則小數點位於x0和x1之間,數值范圍是0<=|x|<=1-2^(-(n-1)),且
,例如1111表示-0.875.
II.定點數加減運算
不論操作數是正還是負,在做補碼加減法時,只需將符號位和數值部分一起參與運算,並且將符號位產生的進位丟掉即可。如:
short A=-9, B=-5;
cout<<A+B<<endl; //-14
推導過程如下:
A的原碼為:1000 0000 0000 1001,因此補碼為:1111 1111 1111 0111
B的原碼為:1000 0000 0000 0101,因此補碼位:1111 1111 1111 1011
A+B的補碼為:1 1111 1111 1111 0010,將符號位產生的進位丟掉,因此最終結果為:
1111 1111 1111 0010,結果的原碼為:1000 0000 0000 1110,即-14。
III.定點數加減運算的溢出判斷
1)用一位符號位判斷溢出
對於加法,只有在正數加正數和負數加負數兩種情況下才可能出現溢出,符號不同的兩個數相加是不會溢出的。
對於減法,只有在正數減負數和負數減正數兩種情況下才可能出現溢出,符號相同的兩個數相減是不會溢出的。
由於減法運算在機器中是用加法器實現的,因此:不論是作加法還是減法,只要實際操作數(減法時即為被減數和“求補”之后的減數)的補碼符號位相同,而結果的符號位又與操作數補碼符號位不同,即為溢出。如:
在4位機中,A=5,B=-4,則A-B溢出,推導過程如下:
A的原碼為0101,補碼為0101;-B的原碼為0100,補碼為0100; A-B的補碼為1001,結果的符號位為1,實際操作數的符號位為0,因此溢出。
2)用兩位符號位判斷溢出
此時判斷溢出的原則是:當2位符號位不同時,表示溢出;否則無溢出。不論是否發生溢出,高位符號位永遠代表真正的符號。如:
x=-0.1011,y=-0.0111,則x+y溢出,推導過程如下:
x的原碼為11.1011,補碼為11.0101;y的原碼為11.0111,補碼為11.1001,因此x+y的補碼為1 10.1110,將符號位產生的進位丟掉,則結果為10.1110,因此溢出。
注:約定整數的符號位與數值位之間用逗號隔開,小數的符號位與數值位之間用小數點隔開。
IV.浮點數
定點數表示法的缺點在於其形式過於僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利於同時表達特別大或特別小的數,最終,絕大多數現代的計算機系統采納了浮點數表達方式,這種表達方式利用科學計數法來表達實數,即用一個尾數(Mantissa,尾數有時也稱為有效數字,它實際上是有效數字的非正式說法),一個基數(Base),一個指數(Exponent)以及一個表示正負的符號來表達實數,比如123.45用十進制科學計數法可以表示為1.2345x102,其中1.2345為尾數,10為基數,2為指數。浮點數利用指數達到了浮動小數點的效果,從而可以靈活地表達更大范圍的實數。
1) IEEE浮點數
在IEEE標准中,浮點數是將特定長度的連續字節的所有二進制位分割為特定寬度的符號域、指數域和尾數域這三個域,域中的值分別用於表示給定二進制浮點數中的符號、指數和尾數,這樣,通過尾數和可以調節的指數就可以表達給定的數值了。
IEEE754指定了:
兩種基本的浮點格式:單精度和雙精度。其中單精度格式具有24位有效數字(即尾數)精度,總共占用32位;雙精度格式具有53位有效數字(即尾數)精度,總共占有64位 。
兩種擴展浮點格式:單精度擴展和雙精度擴展。此標准並未規定這些格式的精確精度和大小,但指定了最小精度和大小,例如IEEE雙精度擴展格式必須至少具有64位有效數字精度,並總共占用至少79位。
具體的格式參見下面的圖例:
2) 單精度格式
IEEE單精度格式由三個字段組成:23位小數f、8位偏置指數e以及1位符號s,這些字段連續存儲在一個32位字中,如下圖所示:
0:22位包含23位小數f,其中第0位是小數的最低有效位,第22位是最高有效位。IEEE標准要求浮點數必須是規范的(浮點數的規范化見后文),這意味着尾數的小數點左側必須位1,因此我們在保存尾數時,可以省略小數點前面的1,從而騰出一個二進制位來保存更多的尾數,這樣我們實際上用23位長的尾數域表達了24位的尾數。
23:30位包含8位偏置指數3,第23位是偏置指數的最低有效位,第30位是最高有效位。8位的指數可以表達0到255之間的256個指數值,但指數可以位正數,也可以為負數,因此為了處理負指數的情況,實際的指數值按要求需要加上一個偏置(Bias)值作為保存在指數域中的值,單精度的偏置值為127(2^7-1),比如單精度的實際指數值0在指數域中保存為127(0+127),實際指數值-63在指數域中保存為64(-63+127)。偏置的引入使得對於單精度數,實際可以表達的指數值的范圍變為-127到128之間(包含兩端),其中指數值-127(保存為全0)以及+128(保存為全1)保留用作特殊值的處理,稍后介紹。如果我們分別用emin和emax來表達其它常規指數值范圍的邊界,即最小指數和最大指數分別用emin和emax來表示,即-126和127,則保留的特殊指數值可以分別表達為emin-1和emax+1;
最高的第31位包含符號位s,s為0表示數值為正數,s位1表示數值為負數。
值得注意的是,對於單精度數,由於我們只有24位的尾數(小數點左側的1被隱藏),所以可以表達的最大尾數為2^24-1=16,777,215,因此單精度的浮點數可以表達的十進制數值中,真正有效的數字不高於8位。
3) 雙精度格式
IEEE雙精度格式由三個字段組成:52位小數f、11位偏置指數e以及1位符號s,這些字段連續存儲在兩個32位字中,如下圖所示:
在SPARC體系結構中,較高地址的32位字包含小數的32位最低有效位,而在x86體系結構中,則是較低地址的32位字包含小數的32位最低有效位。
以x86體系結構為例,則f[31:0]表示小數的32位最低有效位,其中第0位是整個小數的最低有效位。在另一個32位字中,0:19位表示小數的20位最高有效位f[51:32],其中第19位是整個小數的最高有效位;20:30位包含11位偏置指數e,其中第20位是偏置指數的最低有效位,第30位是偏置指數的最高有效位;第31位則是符號位s。上圖將這兩個連續的32位字按一個64位字那樣進行了編號,其中:
0:51位包含52位小數f,其中第0位是小數的最低有效位,第51位是小數的最高有效位。IEEE標准要求浮點數必須是規范的,這意味着尾數的小數點左側必須為1,因此我們在保存尾數時,可以省略小數點前面的1,從而騰出一個二進制位來保存更多的尾數,這樣我們實際上用52位長的尾數域表達了53位的尾數。
52:62位包含11位偏置指數e,第52位是偏置指數的最低有效位,第 62 位是最高有效位。11 位的指數為可以表達 0 到 2047 之間的2048個指數值,但指數可以為正數,也可以為負數,因此為了處理負指數的情況,實際的指數值按要求需要加上一個偏差(Bias)值作為保存在指數域中的值,單精度數的偏差值為1023(2^10-1),偏差的引入使得對於單精度數,實際可以表達的指數值的范圍就變成 -1023到1024之間(包含兩端)。最小指數和最大指數分別用emin 和 emax來表達,稍后將介紹實際的指數值 -1023(保存為全0)以及 +1024(保存為全 1)保留用作特殊值的處理。
最高的第 63 位包含符號位s,s為0表示數值為正數, s為1則表示負數。
4) 雙精度擴展格式(SPARC)
SPARC浮點環境的雙精度格式符合IEEE關於雙精度擴展格式的定義。
SPARC浮點環境的雙精度格式包含三個字段:112位小數f、15位偏置指數e以及1位符號s,這三個字段連續存儲,如下圖所示:
地址最高的32位字包含小數的32位最低有效位,用f[31:0]表示;緊鄰的兩個32位字分別包含f[63:32]和f[95:64];接下來的32位字中, 0:15 位包含小數的16位最高有效位f[111:96],其中第15位是整個小數的最高有效位;16:30位包含15位偏置指數e,其中第16位是該偏置指數的最低有效位,第30位是偏置指數的最高有效位;第 31位包含符號位s。
上圖將這四個連續的32位字按一個128位字那樣進行了編號,其中0:111位存儲小數f;112:126位存儲15位偏置指數e而第 127 位存儲符號位 s。
5) 雙精度擴展格式(x86)
x86浮點環境的雙精度格式符合IEEE關於雙精度擴展格式的定義。
x86浮點環境的雙精度格式包含四個字段:63位小數f、1位顯式前導有效數位j、15位偏置指數e以及1位符號s。在 x86 體系結構系列中,這些字段連續存儲在十個相連地址的8位字節中,由於UNIX System V Application Binary Interface Intel 386 Processor Supplement (Intel ABI) 要求雙精度擴展參數,從而占用堆棧中三個相連地址的32位字,其中地址最高字的16位最高有效位未用,如下圖所示:
地址最低的32位字包含小數的32位最低有效位 f[31:0],其中第0位是整個小數的最低有效位;地址居中的 32 位字中,0:30位包含小數的31位最高有效位 f[62:32],其中第30位是整個小數的最高有效位,第31位包含顯式前導有效數位 j。
地址最高的32位字中,0:14位包含15位偏置指數e,其中第0位是該偏置指數的最低有效位,而第14位是最高有效位;第15位包含符號位s。
VI.浮點數的規范化
同樣的數值可以有多種浮點數表達方式,比如上面例子中的123.45可以表達為12.345x10^1,0.12345x10^3或者1.2345x10^2,因為這種多樣性,有必要對其加以規范化以達到統一表達的目標。規范的(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相當於1x2^3+0x2^2+0x2^1+1x2^0+1x2^-1+0x2^-2+1x2^-3,對應於十進制的 9.625,其規范浮點數表達為1.001101x2^3。
6) 實數和浮點數之間的轉換
假定我們有一個32位的數據,它是一個單精度浮點數,十六進制表示為0xC0B40000,為了得到該浮點數實際表達的實數,我們首先將其轉換為二進制形式:
1100 0000 1011 0100 0000 0000 0000 0000
接着按照浮點數的格式切分為相應的域:
1 1000_0001 0110_1000_0000_0000_0000_000
符號位1表示這是一個負數,指數域為129,意味着實際值為2,尾數域為01101意味着實際的二進制尾數為1.01101,所以實際的實數為:
-1.01101x2^2=-101.101=-5.625
從實數向浮點數變換稍微麻煩一點,假定我們需要將實數-9.625表達為單精度的浮點數格式,方式是首先將它用二進制浮點數表示,然后變換為相應的浮點數格式。
首先,整數部分,即9的二進制形式為1001,小數部分的算法則是將小數部分連續乘以基數2,並記錄結果的整數部分:
0.625x2=1.25 1
0.25x2=0.5 0
0.5x2=1 1
當最后的小數部分為0時,結束該過程,因此小數部分的二進制表達為0.101,這樣我們就得到了完整的二進制形式1001.101,用規范浮點數表示為:
1.001101x2^3
因為是負數,因此符號位為1,指數為3,因此指數域為3+127=130,即二進制的1000 0010,尾數域省掉小數點左側的1,右側用零補齊,得到最終結果為:1 1000_0010 0011_0100_0000_0000_0000_000,最后可以將浮點數表示為十六進制的數據如下:1100 0001 0001 1010 0000 0000 0000 0000,最終結果為0xC11A0000
這里需要注意一個問題,在上面我們有意選擇的示例中,不斷地將產生的小數部分乘以2的過程掩蓋了一個事實,該過程結束的標志是小數部分乘以2的結果為1,但實際上,很多小數根本不能經過有限次這樣的過程而得到結果(比如0.1),但浮點數尾數域的位數是有限的,為此,浮點數的處理方法是持續該過程直到由此得到的尾數足以填滿尾數域,之后對多余的位進行舍入。換句話說,除了我們之前講到的精度問題之外,十進制到二進制的變換也並不能保證總是精確的,而只能是近似值。事實上,只有很少一部分十進制小數具有精確的二進制浮點數表達,再加上浮點數運算過程中的誤差累積,結果是很多我們看來非常簡單的十進制運算在計算機上卻往往出人意料,這就是最常見的浮點運算的"不准確"問題,比如:34.6f-34.0f=0.599998,產生這個誤差的原因是34.6f無法精確的表達為相應的浮點數,而只能保存為經過舍入的近似值,這個近似值與34.0f之間的運算自然無法產生精確的結果(具體過程后面會講)。
7) 舍入
根據標准要求,無法精確保存的值必須向最接近可保存的值進行舍入,這有點像我們熟悉的十進制的四舍五入,即不足一半則舍,一半以上(包括一半)則進,不過對於二進制浮點數而言,則是0就舍,但1不一定進,而是在前后兩個等距接近的可保存的值中,取其中最后一位有效數字為零的值進行保存,即采取向偶數舍入,比如0.5要舍到0,1.5要入到2(即先試着進1,會得到最后結果,如果這個結果的尾數的最后位為0,則進位成功;否則進位失敗,直接舍去),看下面幾個例子:
結果推導分析:
16777215f的分析過程:
1111 1111 1111 1111 1111 1111 16777215f(其中第一個1會隱藏)
1.1111_1111 1111_1111_1111_111 剛好是23位小數,不會丟失精度,能精確表示
0 23+127 1111_1111_1111_1111_1111_111 (0表示是正數,23+127表示偏移指數值,尾數位為23個1)
0 10010110 1111_1111_1111_1111_1111_111
16777216f的分析過程:
1000 0000 0000 0000 0000 0000 0 16777216f(最后一個0會被舍去)
1.0000_0000_0000_0000_0000_000 0 (因為尾數域為23位,因此最后一個0被舍去,但是還是能准確顯示)
0 24+127 0000_0000_0000_0000_0000_000
0 10010111 0000_0000_0000_0000_0000_000
16777217f的分析過程:
1000 0000 0000 0000 0000 0000 1 16777217f(最后一個1會被舍去)
1.0000_0000_0000_0000_0000_000 1 (最后一個1被舍去,試着進位)
1. 0000_0000_0000_0000_0000_001 (由於進位后,尾數域末尾不為0,因此進位失敗,直接將1舍去)
1. 0000_0000_0000_0000_0000_000 (這里其實已經說明結果為16777216f)
16777218f的分析過程:
1000 0000 0000 0000 0000 0001 0 16777218f(最后一個0會被舍去)
1. 0000_0000_0000_0000_0000_001 0 (最后一個0被舍去,所以還是能准確顯示)
0 24+127 0000_0000_0000_0000_0000_001
0 10010111 0000_0000_0000_0000_0000_001
16777219f的分析過程:
1000 0000 0000 0000 0000 0001 1 16777219f(最后一個1會被舍去)
1. 0000_0000_0000_0000_0000_001 1 (最后一個1被舍去,嘗試進位)
1. 0000_0000_0000_0000_0000_010 (尾數域末位為0,進位成功)
0 24+127 0000_0000_0000_0000_0000_010
010010111 0000_0000_0000_0000_0000_010
… …
8) 浮點數的加減運算
浮點數的加減運算一般由以下五個步驟完成:對階、尾數運算、結果規格化、舍入處理、溢出判斷。
tip1.對階
對階的目的是使兩操作數的小數點位置對齊,即使兩數的階碼相等。為此,首先要求出階差,再按小階向大階看齊的原則,使階小的尾數向右移位,每右移一位,階碼加1,直到兩數的階碼相等為止。
tip2.尾數運算
尾數運算就是將對階后的尾數按定點加減運算規則進行運算。
tip3.結果規格化
在機器中,為保證浮點數表示的唯一性,浮點數在機器中都是以規格化形式存儲的。對於IEEE754標准的浮點數來說,就是尾數必須是1.xxxx的形式。由於在進行上述兩個定點小數的尾數相加減運算后,尾數有可能是非規格化形式,為此必須進行規格化操作,規格化操作包括左規和右規兩種情況。
左規:將尾數左移一位,同時階碼減1,直至尾數成為1.xxxx的形式;
右規:將尾數右移一位,同時階碼增1,便成為規格化的形式了。
注:右規操作只需將尾數右移一位即可,這種情況出現在尾數的最高位(即小數點前一位)運算時出現了進位,使尾數成為10.xxxx或11.xxxx的形式。
tip4.舍入處理
在對階和右規過程中,可能會將尾數的低位丟失,引起誤差,影響精度,為此可用舍入法來提高尾數的精度。IEEE754標准列出了四種可選的舍入處理方法:
就近舍入(round to neareset):這是標准列出的默認舍入方式,前面有講。
朝+∞舍入(round toward +∞):對正數來說,只要多余位不為全0,則向尾數最低有效位進1;對負數來說,則是簡單地舍去。
朝-∞舍入(round toward -∞):與朝+∞舍入方法正好相反,對正數來說,只是簡單地舍去;對負數來說,只要多余位不為全0,則向尾數最低有效位進1。
朝0舍入(round toward 0): 即簡單地截斷舍去,而不管多余位是什么值。這種方法實現簡單,但容易形成累積誤差,且舍入處理后的值總是向下偏差。
tip5.溢出判斷
與定點數運算不同的是,浮點數的溢出是以其運算結果的階碼的值是否產生溢出來判斷的。若階碼的值超過了階碼所能表示的最大正數,則為上溢,進一步,若此時浮點數為正數,則為正上溢,記為+∞,若浮點數為負數,則為負上溢,記為-∞;若階碼的值超過了階碼所能表示的最小負數,則為下溢,進一步,若此時浮點數為正數,則為正下溢,若浮點數為負數,則為負下溢。正下溢和負下溢都作為機器零處理,即將尾數各位強置為零。
/*浮點數加減運算-例1*/
設兩浮點數的IEEE754標准存儲格式分別為
x=0 10000010 01101100000000000000000,y=0 10000100 01011101100000000000000,求x+y,並給出結果的IEEE754標准存儲格式。
解:
對於浮點數x,
符號位s=0
指數e=E-127= 10000010-01111111=00000011=(3)10
尾數m=1.01101100000000000000000
於是有 x=+1.01101100000000000000000×23
對於浮點數y:
符號位s=0
指數e=E-127=10000100-01111111=00000011=(5)10
尾數m=1.01011101100000000000000
於是有
y=+1.01011101100000000000000×25
(1)對階
⊿E=Ex-Ey=3-5=-2 ,因此x=1.01101100000000000000000×23=
0.01011011000000000000000×25
(2)尾數相加
x+y=00.01011011000000000000000×25 +01.01011101100000000000000×25
=01.10111000100000000000000×25(其中最高位是符號位)
結果的IEEE754標准存儲格式為:0 10000100 10111000100000000000000
/*浮點數加減運算-例2*/
求34.6f-34.0f
解:
對於浮點數34.6f:
符號位s=0
指數e=(5)10
尾數m=1. 000101001100110011001100…,根據就近舍入,得到:
m'=1. 00010100110011001100110
對於浮點數34.0f:
符號位s=0
指數e=(5)10
尾數m=1.00010000000000000000000
(1)對階
階碼相同
(2)尾數相加
34.6f-34.0f = 01. 00010100110011001100110×25 -11.00010000000000000000000×25
= 01. 00010100110011001100110×25 + 10.11110000000000000000000×25
= 100.00000100110011001100110×25
= 00.00000100110011001100110×25(符號位的進位1被舍去)
(3)左規
34.6f-34.0f = 1.00110011001100110000000×2-1 = (0.59999847)10
結果的IEEE754標准存儲格式為:0 01111110 00110011001100110000000
/*浮點數加減運算-例3*/
求1.5f-2.4f
解:
對於浮點數1.5f:
符號位s=0
指數e=(0)10
尾數m=1.10000000000000000000000
對於浮點數2.4f:
符號位s=0
指數e=(1)10
尾數m=1.00110011001100110011010
(1)對階
⊿E=0-1=-1 ,因此1.5f=0.110000000000000000000000×21
(2)尾數相加
1.5f-2.4f = 00.110000000000000000000000×21 - 11.00110011001100110011010×21
= 00.110000000000000000000000×21 + 10.11001100110011001100110×21
= 11.10001100110011001100110×21(其中最高位是符號位)
= 10.01110011001100110011010×21(將補碼轉換成原碼)
(3)左規
1.5f-2.4f = -1.11001100110011001101000×2-1 = (-0.90000010)10
結果的IEEE754標准存儲格式為:1 01111110 11001100110011001101000
9) 特殊值-NaN
當指數為128(指數域全1),且尾數域不等於0時,該浮點數即為NaN。
IEEE標准沒有要求具體的尾數域,所以NaN實際上不是一個,而是一族。
比較操作符<、<=、>、>=在任一操作數為NaN時均返回false,等於操作符==在任一操作數為NaN時均返回false,即使是兩個具有相同位模式的NaN也一樣,而操作符!=則當任一操作數為NaN時均返回true,這個規則的一個有趣的結果是x!=x,當x為NaN時竟然為真。
用特殊的NaN來表達上述運算錯誤的意義在於避免了因這些錯誤而導致運算的不必要的終止。比如,如果一個被循環調用的浮點運算方法,可能由於輸入的參數問題而導致發生這些錯誤,NaN使得即使某次循環發生了這樣的錯誤,也可以簡單地繼續執行循環以進行那些沒有錯誤的運算。你可能想到,既然Java有異常處理機制,也許可以通過捕獲並忽略異常達到相同的效果。但是,要知道,IEEE標准不是僅僅為Java而制定的,各種語言處理異常的機制不盡相同,這將使得代碼的遷移變得更加困難。何況,不是所有語言都有類似的異常或者信號(Signal)處理機制。
10) 特殊值-無窮
當指數為128(指數域全1),且尾數域等於0時,該浮點數即為無窮大,用符號位來確定是正無窮大還是負無窮大。
無窮用於表達計算中產生的上溢(Overflow)問題。比如兩個極大的數相乘時,盡管兩個操作數本身可以用保存為浮點數,但其結果可能大到無法保存為浮點數,而必須進行舍入。根據IEEE標准,此時不是將結果舍入為可以保存的最大的浮點數(因為這個數可能離實際的結果相差太遠而毫無意義),而是將其舍入為無窮。對於負數結果也是如此,只不過此時舍入為負無窮,也就是說符號域為1的無窮。有了NaN的經驗我們不難理解,特殊值無窮使得計算中發生的上溢錯誤不必以終止運算為結果。
無窮和除NaN以外的其它浮點數一樣是有序的,從小到大依次為負無窮,負的有窮非零值,正負零(隨后介紹),正的有窮非零值以及正無窮。除NaN以外的任何非零值除以零,結果都將是無窮,而符號則由作為除數的零的符號決定。
回顧我們對NaN的介紹,當零除以零時得到的結果不是無窮而是NaN。原因不難理解,當除數和被除數都逼近於零時,其商可能為任何值,所以IEEE標准決定此時用NaN作為商比較合適。
11) 特殊值-有符號的零
因為IEEE標准的浮點數格式中,小數點左側的1是隱藏的,而零顯然需要尾數必須是零。所以,零也就無法直接用這種格式表達而只能特殊處理。
當指數為-127(指數域全0),且尾數域等與0時,該浮點數即為零,考慮到符號域的作用,所以存在着兩個零,即+0和-0。不同於正負無窮之間是有序的,IEEE標准規定正負零是相等的。
零有正負之分,的確非常容易讓人困惑,這一點是基於數值分析的多種考慮,經利弊權衡后形成的結果。有符號的零可以避免運算中,特別是涉及無窮的運算中,符號信息的丟失。舉例而言,如果零無符號,則等式1/(1/x)=x當x=±∞時不再成立。原因是如果零無符號,1和正負無窮的比值為同一個零,然后1與0的比值為正無窮,符號沒有了。解決這個問題,除非無窮也沒有符號,但是無窮的符號表達了上溢發生在數軸的哪一側,這個信息顯然是不能不要的,因此零有符號。
public class test
{
public static void main(String[] args)
{
//指數域全0,尾數域為0時,浮點數為有符號的零
System.out.println(Float.intBitsToFloat(0x0));
//指數域全1,尾數域為0時,浮點數為無窮,符號位決定是正無窮還是負無窮
System.out.println(Float.intBitsToFloat(0x7F800000));
System.out.println(Float.intBitsToFloat(0xFF800000));
//指數域全1,尾數域不為0時,浮點數為NaN
System.out.println(Float.intBitsToFloat(0x7FC00000));
System.out.println(Float.intBitsToFloat(0x7F800001));
System.out.println(Float.intBitsToFloat(0xFF800001));
}
}
解釋一下幾個方法:
Float.floatToIntBits(float f):按照IEEE754標准,返回指定浮點數的表達形式。舉個例子:Float.floatToIntBits(20.5f)按照如下方式計算:20.5D=10100.1B=1.01001x2^4,因此可將浮點數20.5f表示為十六進制數據:0100 0001 1010 0100 0000 0000 0000 0000,轉換成10進制即1101266944
Float.intBitsToFloat(int i):按照IEEE754標准,返回指定整數的表達形式。舉個例子:Float.intBitsToFloat(1101266944)即為20.5f
Integer.toBinaryString(int i):將指定整數表示成相應的二進制數,舉個例子:
Ineger.toBinaryString(128)即為10000000
注:javac編譯編碼GBK的不可映射字符時,可以通過-encoding來指定編碼方式,如:
javac -encoding utf-8 test.java
12) 非規范化數
這個數的定義和有符號0一樣,不過尾數不能為0,用於小出范圍的數。
我們來考察浮點數的一個特殊情況。選擇兩個絕對值極小的浮點數,以單精度的二進制浮點數為例,比如1.001×2^-125和1.0001×2^-125這兩個數(分別對應於十進制的2.6448623×10^-38和2.4979255×10^-38)。顯然,他們都是普通的浮點數(指數為-125,大於允許的最小值-126;尾數更沒問題),按照IEEE754可以分別保存為0 00000010 00100000000000000000000(0x1100000)和0 00000010 00010000000000000000000(0x1080000)。
現在我們看看這兩個浮點數的差值。
不難得出,該差值為0.0001×2^-125,表達為規范浮點數則為1.0×2^-129。問題在於其指數小於允許的最小指數值,所以無法保存為規范浮點數,最終只能近似為零(FlushtoZero)。這種特殊情況意味着下面本來十分可靠的代碼也可能出現問題:
if(x!=y)
{
z=1/(x-y);
}
正如我們精心選擇的兩個浮點數展現的問題一樣,即使x不等於y,x和y的差值仍然可能絕對值過小,而近似為零,導致除以0的情況發生。
為了解決此類問題,IEEE標准中引入了非規范(Denormalized)浮點數。規定當浮點數的指數為允許的最小指數值,即emin時,尾數不必是規范化的。比如上面例子中的差值可以表達為非規范的浮點數0.001×2^-126,其中指數-126等於emin。為了保存非規范浮點數,IEEE標准采用了類似處理特殊值零時所采用的辦法,即用特殊的指數域值emin-1加以標記,當然,此時的尾數域不能為零。這樣,例子中的差值可以保存為00000000000100000000000000000000(0x100000),沒有隱含的尾數位。
有了非規范浮點數,去掉了隱含的尾數位的制約,可以保存絕對值更小的浮點數。而且,也由於不再受到隱含尾數域的制約,上述關於極小差值的問題也不存在了,因為所有可以保存的浮點數之間的差值同樣可以保存。
注意,規定的是"不必",這也就意味着"可以",因此當浮點數實際的指數為emin,該浮點數仍是規范的,也就是說,保存時隱含着一個隱藏的尾數位。