浮點數
浮點計算
是指浮點數參與的運算,這種運算通常伴隨著因為無法精確表示而進行的近似或捨入。
一個浮點數a由兩個數m和e來表示:a = m × b^e。在任意一個這樣的系統中,我們選擇一個
基數b(記數系統的基)和精度p(即使用多少位來存儲)。m(即尾數)是形如±d.ddd...ddd的p位數(每一位是一個介於0到b-1之間的
整數,包括0和b-1)。如果m的第一位是非0整數,m稱作
規格化的。有一些描述使用一個單獨的符號位(s 代表+或者-)來表示正負,這樣m必須是正的。e是指數。
結構
由此可以看出,在計算機中表示一個浮點數,其結構如下:
這種設計可以在某個固定長度的存儲空間內表示定點數無法表示的更大範圍的數。
浮點加法減法運算
設有兩個浮點數x和y,它們分別為
x = Mx*2^Ex
y = My*2^Ey
其中Ex和Ey分別為數x和y的階碼,Mx和My為數x和y的
尾數。
兩浮點數進行加法和減法的運算規則是
設 Ex小於等於Ey,則 x±y = (Mx*2^(Ex-Ey)±My)*2^Ey,
完成浮點加減運算的操作過程大體分為四步:
1. 0 運算元的檢查;
⑴ 0 運算元檢查
浮點加減運算過程比
定點運算過程複雜。如果判知兩個運算元x或y中有一個數為0,即可得知運算結果而沒有必要再進行後續的一系列操作以節省運算時間。0運算元檢查步驟則用來完成這一功能。
兩浮點數進行加減,首先要看兩數的階碼是否相同,即
小數點位置是否對齊。若二數階碼相同,表示
小數點是對齊的,就可以進行尾數的加減運算。反之,若二數階碼不同,表示小數點位置沒有對齊,此時必須使二數階碼相同,這個過程叫作對階。
△E = Ex-Ey
若△E=0,表示兩數階碼相等,即Ex=Ey;若△E>0,表示Ex>Ey;若△E<0,表示Ex<Ey。
當Ex≠Ey 時,要通過
尾數的移動以改變Ex或Ey,使之相等。原則上,既可以通過Mx移位以改變Ex來達到Ex=Ey,也可以通過My移位以改變Ey來實現Ex=Ey。但是,由於浮點表示的數多是
規格化的,尾數左移會引起
最高有效位的丟失,造成很大誤差。
尾數右移雖引起最低有效位的丟失,但造成誤差較小。因此,
對階操作規定使
尾數右移,尾數右移後階碼作相應增加,其數值保持不變。顯然,一個增加後的階碼與另一個階碼相等,增加的階碼的一定是小階。因此在
對階時,總是使小階向大階看齊,即小階的尾數向右移位(相當於小數點左移)每右移一位,其階碼加1,直到兩數的階碼相等為止,右移的位數等於階差△E。
⑶ 尾數求和運算
對階結束後,即可進行尾數的求和運算。不論
加法運算還是減法運算,都按加法進行操作,其方法與定點加減法運算完全一樣。
在浮點加減運算時,
尾數求和的結果也可以得到01.ф…ф或10.ф…ф,即兩符號位不等,這在定點加減法運算中稱為溢出,是不允許的。但在浮點運算中,它表明
尾數求和結果的絕對值大於1,向左破壞了
規格化。此時將運算結果右移以實現
規格化表示,稱為向右規格化。規則是:
尾數右移1位,階碼加1。當
尾數不是1.M時需向左
規格化。
⑸ 捨入處理
在
對階或向右規格化時,
尾數要向右移位,這樣,被右移的尾數的低位部分會被丟掉,從而造成一定
誤差,因此要進行捨入處理。
簡單的捨入方法有兩種:一種是"0舍1入"法,即如果右移時被丟掉
數位的最高位為0則捨去,為1則將尾數的末位加"1"。另一種是"恆置一"法,即只要數位被移掉,就在
尾數的末尾恆置"1"。
就近捨入其實質就是通常所說的"四捨五入"。例如,
尾數超出規定的23位的多餘位數字是10010,多餘位的值超過規定的最低有效位值的一半,故最低有效位應增1。若多餘的5位 是01111,則簡單的截尾即可。對多餘的5位10000這種特殊情況:若最低有效位現為0,則截 尾;若最低有效位現為1,則向上進一位使其變為 0。
朝0捨入 即朝
數軸原點方向捨入,就是簡單的截尾。無論
尾數是正數還是負數,截尾都使取值的
絕對值比原值的絕對值小。這種方法容易導致誤差積累。
朝+
∞捨入 對
正數來說,只要多餘位不全為0則向最低有效位進1;對
負數來說則是簡單的截尾。
朝-∞捨入 處理方法正好與 朝+∞捨入情況相反。對
正數來說,只要多餘位不全為0則簡單截尾;對
負數來說,向最低有效位進1。
⑹ 溢出處理
浮點數的溢出是以其階碼溢出表現出來的。在加\減運算過程中要檢查是否產生了溢出:若階碼正常,加(減)運算正常結束;若階碼溢出,則要進行相應處理。另外對
尾數的溢出也需要處理。
階碼上溢 超過了階碼可能表示的最大值的正指數值,一般將其認為是+∞和-∞。
階碼下溢 超過了階碼可能表示的最小值的負指數值,一般將其認為是0。
尾數
上溢 兩個同符號尾數相加產生了最高位向上的進位,將尾數右移,階碼增1來重新對齊。
尾數下溢 在將尾數右移時,尾數的最低有效位從尾數域右端流出,要進行捨入處理。
實例
題目
例如,一個指數範圍為±4的4位十進制浮點數可以用來表示43210,4.321或0.0004321,但是沒有足夠的精度來表示432.123和43212.3(必須近似為432.1和43210)。當然,實際使用的位數通常遠大於4。
特別數值
此外,浮點數表示法通常還包括一些特別的數值:+∞和−∞(正負
無窮大)以及NaN('Not a Number')。無窮大用於數太大而無法表示的時候,NaN則指示非法操作或者無法定義的結果。
二進制表示
眾所周知,計算機中的所有數據都是以
二進制表示的,浮點數也不例外。然而浮點數的二進制表示法卻不像
定點數那么簡單了。
浮點數概念
先澄清一個概念,浮點數並不一定等於
小數,定點數也並不一定就是整數。所謂浮點數就是小數點在邏輯上是不固定的,而
定點數只能表示小數點固定的數值,具用浮點數或定點數表示某哪一種數要看用戶賦予了這個數的意義是什麼。
C++中的浮點數有6種,分別是:
unsigned float:
單精度無符號,32位
double:雙精度,64位
然而不同的
編譯器對它們的支持也略有不同,據我所知,很多編譯器都沒有按照IEEE規定的標準80位支持後兩種浮點數的,大多數編譯器將它們視為double,或許還有極個別的編譯器將它們視為128位?!對於128位的long double我也僅是聽說過,沒有求證,哪位高人知道這一細節煩勞告知。
下面我僅以float(帶符號,
單精度,32位)類型的浮點數說明C++中的浮點數是如何在記憶體中表示的。先講一下基礎知識,
純小數的二進制表示。(純小數就是沒有
整數部分的小數,講給國小沒好好學的人)
純小數要想用二進制表示,必須先進行
規格化,即化為 1.xxxxx * ( 2 ^ n ) 的形式(“^”代表乘方,2 ^ n表示2的n次方)。對於一個純小數D,求n的公式如下:
n = 1 + log2(D); // 純小數求得的n必為負數
再用 D / ( 2 ^ n ) 就可以得到
規格化後的小數了。接下來就是
十進制到
二進制的轉化問題,為了更好的理解,先來看一下10進制的純小數是怎么表示的,假設有純小數D,它
小數點後的每一位數字按順序形成一個
數列:
{k1,k2,k3,...,kn}
那么D又可以這樣表示:
D = k1 / (10 ^ 1 ) + k2 / (10 ^ 2 ) + k3 / (10 ^ 3 ) + ... + kn / (10 ^ n )
推廣到二進制中,純小數的表示法即為:
D = b1 / (2 ^ 1 ) + b2 / (2 ^ 2 ) + b3 / (2 ^ 3 ) + ... + bn / (2 ^ n )
現在問題就是怎樣求得b1,b2,b3,……,bn。算法描述起來比較複雜,還是用數字來說話吧。聲明一下,1 / ( 2 ^ n )這個數比較特殊,我稱之為位階值。
例二
例如0.456,第1位,0.456小於位階值0.5故為0;第2位,0.456大於位階值0.25,該位為1,並將0.456減去0.25得0.206進下一位;第3位,0.206大於位階值0.125,該位為1,並將0.206減去0.125得0.081進下一位;第4位,0.081大於0.0625,為1,並將0.081減去0.0625得0.0185進下一位;第5位0.0185小於0.03125……
最後把計算得到的足夠多的1和0按位順序組合起來,就得到了一個比較精確的用二進制表示的純小數了,同時精度問題也就由此產生,許多數都是無法在有限的n內完全精確的表示出來的,我們只能利用更大的n值來更精確的表示這個數,這就是為什麼在許多領域,
程式設計師都更喜歡用double而不是float。
float的記憶體結構,我用一個帶位域的結構體描述如下:
struct MYFLOAT
{
bool bSign : 1; // 符號,表示正負,1位
char cExponent : 8; // 指數,8位
unsigned long ulMantissa : 32; //
尾數,32位
};
符號就不用多說了,1表示負,0表示正
指數是以2為底的,範圍是 -128 到 127,實際數據中的指數是原始指數加上127得到的,如果超過了127,則從-128開始計,其行為和X86架構的CPU處理加減法的溢出是一樣的。
比如:127 + 2 = -127;-127 - 2 = 127
尾數都省去了第1位的1,所以在還原時要先在第一位加上1。它可能包含整數和純小數兩部分,也可能只包含其中一部分,視數字大小而定。對於帶有整數部分的浮點數,其整數的表示法有兩種,當整數大於十進制的16777215時使用的是
科學計數法,如果小於或等於則直接採用一般的二進制表示法。
科學計數法和小數的表示法是一樣的。
小數部分則是直接使用
科學計數法,但形式不是X * ( 10 ^ n ),而是X * ( 2 ^ n )。拆開來看。
0 000000000000000000000000000000
符號位 指數位 尾數位
--------------------------------------------------------------------------------
例三
判斷兩個浮點數是否相等。
在這個例子中我們以C++代碼來判別兩個浮點數是否相等。由於浮點數在存儲中無法精確表示,所以 fp1==fp2 無法準確的判斷float型變數fp1與fp2是否相等。應該使用 (fp1-fl2)<0.0000001 來進行判斷。
示例:
bool equal(float fp1,float fp2)
{
if( abs( fp1 - fp2 ) < 0.00000001 ) return true;
else
return false;
}
--------------------------------------------------------------------------------
導數字分布
簡介
作者:concreteHAM
實例
例如:
2.345 E 67這是一個十進制
規格化浮點數,前導數字就是2。
就只有一個“隨機”的浮點數而言,討論其分散式沒有意義的,我們要討論的是充分多個“隨機”數進行的一系列運算後產生的浮點結果的前導數字分布。
假設現在有一巨大的浮點數集,依此對數集中每個浮點數都乘以2,其中有一個十進制浮點數F,它的前導數字是1,那么它底數可能的值範圍就是1.000…~1.999…,乘以一個數字2,那么它的底數就變成2.000…~3.999…,很明顯乘以2以前前導數字是1的浮點個數與現在前導數字是2、3的浮點個數相同。以此我們接下來分析。
對於一個b
進制的浮點數,它的前導數字x範圍就是0 < x < b,設f(x)是上述數集的前導數字的
機率密度函式(註:是密度函式),那么它在前導數字u和v之間(0<u<v<b)的機率就是:
∫[u,v]f(x)dx ⑴
由前面所述的,對於一個充分小增量Δx,f(x)必須滿足這樣一個公式:
f⑴Δx = x*f(x)Δx ⑵
因為:
f⑴Δx是f⑴微分段內的機率,根據前面所述,f⑴Δx機率等於f(1*x)*(x*Δx)
很明顯:
f(x) = f⑴/x ⑶
兩邊在[1,b]之間進行積分,等號左邊必定為1,右邊等於f⑴ln(b):
1 = f⑴ln(b) ⑷
得:f⑴ = 1/ln(b) 帶入⑶中:
f(x) = 1/(x*ln(b))
那么利用⑴式得:
∫[u,v]1/(x*ln(b))dx
= ln(v/u) / ln(b) ⑸
這就是求前導數字的機率分布函式。
= ln((1+1)/1) / ln⑽
≈ 0.301
前導數字為9的機率就是:
= ln((9+1)/9) / ln⑽
≈0.0458
以下是一個測試程式(Mathematica軟體):
T[n_,b_]:=Block[{res={},ran,i,a},
For[i=1,i<b,i++;
res=Append[res,0]
];
For[i=0,i<n,i++;
ran=Random[]*Random[]*Random[]; 充分打亂浮點數
ran=Log[b,ran];
a=Floor[b^(ran-Floor[ran])]; 取出前導數字
res[[a]]++ 對前導數字個數統計
];
Return[res]
]
執行T[100000,10],以10
進制測試100000個浮點數,得到一個分布:
{30149,18821,13317,9674,7688,6256,5306,4655,4134}
和理論值相當接近。