眾所周知,計(jì)算機(jī)中的所有數(shù)據(jù)都是以二進(jìn)制表示的,浮點(diǎn)數(shù)也不例外。然而浮點(diǎn)數(shù)的二進(jìn)制表示法卻不像定點(diǎn)數(shù)那么簡(jiǎn)單了。
先澄清一個(gè)概念,浮點(diǎn)數(shù)并不一定等于小數(shù),定點(diǎn)數(shù)也并不一定就是整數(shù)。所謂浮點(diǎn)數(shù)就是小數(shù)點(diǎn)在邏輯上是不固定的,而定點(diǎn)數(shù)只能表示小數(shù)點(diǎn)固定的數(shù)值,具用浮點(diǎn)數(shù)或定點(diǎn)數(shù)表示某哪一種數(shù)要看用戶賦予了這個(gè)數(shù)的意義是什么。
C++中的浮點(diǎn)數(shù)有6種,分別是:
float:?jiǎn)尉龋?2位
unsigned float:?jiǎn)尉葻o(wú)符號(hào),32位
double:雙精度,64位
unsigned double:雙精度無(wú)符號(hào),64位
long double:高雙精度,80位
unsigned long double:高雙精度無(wú)符號(hào),80位(嚯,應(yīng)該是C++中最長(zhǎng)的內(nèi)置類型了吧?。?
然而不同的編譯器對(duì)它們的支持也略有不同,據(jù)我所知,很多編譯器都沒(méi)有按照IEEE規(guī)定的標(biāo)準(zhǔn)80位支持后兩種浮點(diǎn)數(shù)的,大多數(shù)編譯器將它們視為double,或許還有極個(gè)別的編譯器將它們視為128位?!對(duì)于128位的long double我也僅是聽(tīng)說(shuō)過(guò),沒(méi)有求證,哪位高人知道這一細(xì)節(jié)煩勞告知。
下面我僅以float(帶符號(hào),單精度,32位)類型的浮點(diǎn)數(shù)說(shuō)明C++中的浮點(diǎn)數(shù)是如何在內(nèi)存中表示的。先講一下基礎(chǔ)知識(shí),純小數(shù)的二進(jìn)制表示。(純小數(shù)就是沒(méi)有整數(shù)部分的小數(shù),講給小學(xué)沒(méi)好好學(xué)的人)
純小數(shù)要想用二進(jìn)制表示,必須先進(jìn)行規(guī)格化,即化為 1.xxxxx * ( 2 ^ n ) 的形式(“^”代表乘方,2 ^ n表示2的n次方)。對(duì)于一個(gè)純小數(shù)D,求n的公式如下:
n = 1 + log2(D); // 純小數(shù)求得的n必為負(fù)數(shù)
再用 D / ( 2 ^ n ) 就可以得到規(guī)格化后的小數(shù)了。接下來(lái)就是十進(jìn)制到二進(jìn)制的轉(zhuǎn)化問(wèn)題,為了更好的理解,先來(lái)看一下10進(jìn)制的純小數(shù)是怎么表示的,假設(shè)有純小數(shù)D,它小數(shù)點(diǎn)后的每一位數(shù)字按順序形成一個(gè)集合:
{k1, k2, k3, ... , kn}
那么D又可以這樣表示:
D = k1 / (10 ^ 1 ) + k2 / (10 ^ 2 ) + k3 / (10 ^ 3 ) + ... + kn / (10 ^ n )
推廣到二進(jìn)制中,純小數(shù)的表示法即為:
D = b1 / (2 ^ 1 ) + b2 / (2 ^ 2 ) + b3 / (2 ^ 3 ) + ... + bn / (2 ^ n )
現(xiàn)在問(wèn)題就是怎樣求得b1, b2, b3,……,bn。算法描述起來(lái)比較復(fù)雜,還是用數(shù)字來(lái)說(shuō)話吧。聲明一下,1 / ( 2 ^ n )這個(gè)數(shù)比較特殊,我稱之為位階值。
例如0.456,第1位,0.456小于位階值0.5故為0;第2位,0.456大于位階值0.25,該位為1,并將0.45減去0.25得0.206進(jìn)下一位;第3位,0.206大于位階值0.125,該位為1,并將0.206減去0.125得0.081進(jìn)下一位;第4位,0.081大于0.0625,為1,并將0.081減去0.0625得0.0185進(jìn)下一位;第5位0.0185小于0.03125……
最后把計(jì)算得到的足夠多的1和0按位順序組合起來(lái),就得到了一個(gè)比較精確的用二進(jìn)制表示的純小數(shù)了,同時(shí)精度問(wèn)題也就由此產(chǎn)生,許多數(shù)都是無(wú)法在有限的n內(nèi)完全精確的表示出來(lái)的,我們只能利用更大的n值來(lái)更精確的表示這個(gè)數(shù),這就是為什么在許多領(lǐng)域,程序員都更喜歡用double而不是float。
float的內(nèi)存結(jié)構(gòu),我用一個(gè)帶位域的結(jié)構(gòu)體描述如下:
struct MYFLOAT
{
bool bSign : 1; // 符號(hào),表示正負(fù),1位
char cExponent : 8; // 指數(shù),8位
unsigned long ulMantissa : 23; // 尾數(shù),23位
};
符號(hào)就不用多說(shuō)了,1表示負(fù),0表示正
指數(shù)是以2為底的,范圍是 -128 到 127,實(shí)際數(shù)據(jù)中的指數(shù)是原始指數(shù)加上127得到的,如果超過(guò)了127,則從-128開(kāi)始計(jì),其行為和X86架構(gòu)的CPU處理加減法的溢出是一樣的。比如:127 + 2 = -127;127 - 2 = 127
尾數(shù)都省去了第1位的1,所以在還原時(shí)要先在第一位加上1。它可能包含整數(shù)和純小數(shù)兩部分,也可能只包含其中一部分,視數(shù)字大小而定。對(duì)于帶有整數(shù)部分的浮點(diǎn)數(shù),其整數(shù)的表示法有兩種,當(dāng)整數(shù)大于十進(jìn)制的16777215時(shí)使用的是科學(xué)計(jì)數(shù)法,如果小于或等于則直接采用一般的二進(jìn)制表示法??茖W(xué)計(jì)數(shù)法和小數(shù)的表示法是一樣的。
小數(shù)部分則是直接使用科學(xué)計(jì)數(shù)法,但形式不是X * ( 10 ^ n ),而是X * ( 2 ^ n )。拆開(kāi)來(lái)看。
0 00000000 0000000000000000000000
符號(hào)位 指數(shù)位 尾數(shù)位
下面是一個(gè)分析float類型內(nèi)存數(shù)據(jù)的程序,經(jīng)測(cè)試是完好的,如果發(fā)現(xiàn)有問(wèn)題,請(qǐng)?zhí)岢鰜?lái),我好改進(jìn)。
#include <iostream>
#include <iomanip>
using namespace std;
int _tmain( int argc, _TCHAR* argv[] )
{
// 有符號(hào)的32位float類型浮點(diǎn)數(shù)變量的定義及數(shù)據(jù)初始化,其值可任意修改
float fDigital = 0.0f;
// 臨時(shí)變量,用于存儲(chǔ)浮點(diǎn)數(shù)的內(nèi)存數(shù)據(jù)
unsigned long nMem;
// 將內(nèi)存按位復(fù)制到臨時(shí)變中,以便取用。
nMem = *(unsigned long*)&fDigital;
// 以小數(shù)點(diǎn)后保留8個(gè)小數(shù)點(diǎn)的精度輸出原始浮點(diǎn)數(shù)
cout << setprecision( 8 );
cout << "浮點(diǎn)數(shù):" << fDigital << endl;
cout << "-----------------------" << endl;
// 判斷是否為0,全部位都為0的浮點(diǎn)數(shù)據(jù)表示0,無(wú)分析意義
if ( nMem != 0 )
{
// 打印出其符號(hào)。
// 最高1位為符號(hào)位。用bool來(lái)表示,true表示負(fù)數(shù),false表示正數(shù)。
// 和最高位為1的數(shù)據(jù)0x80000000進(jìn)行按位與,可得到其符號(hào)。
bool bNegative = ( ( nMem & 0x80000000L ) != 0 );
// 如果是負(fù)數(shù),則輸入負(fù)號(hào),否則輸出空格。
cout << "符號(hào):" << ( bNegative ? '-' : '+' ) << endl;
// 打印出其指數(shù)。
// 第30 - 23位是指數(shù)位,是有正負(fù)的8位整數(shù)數(shù)據(jù),用char來(lái)表示。
// 將內(nèi)存右移一位,再左移24位,再硬性截?cái)酁?位即可得到指數(shù)原始數(shù)據(jù)。
char cExponent = (char)( ( nMem << 1 ) >> 24 );
// IEEE浮點(diǎn)數(shù)表示法規(guī)定,原指數(shù)加127為內(nèi)存中的指數(shù)。
// 將原始指數(shù)數(shù)據(jù)減127得到其真實(shí)指數(shù)(CPU會(huì)自動(dòng)處理上下界溢出)。
cExponent -= 127;
// 以10進(jìn)制帶符號(hào)整數(shù)方式輸出指數(shù)數(shù)據(jù)
cout << "指數(shù):" << (int)cExponent << endl;
// 打印出其尾數(shù)。
// 第22 - 0位是尾數(shù)位,由于省去了頭一位1,因此應(yīng)該是24位無(wú)符號(hào)數(shù)據(jù)。
// 用無(wú)符號(hào)長(zhǎng)整型來(lái)表示。
// 和最低22位都是1的數(shù)據(jù)0x7FFFFF進(jìn)行按位與,可得到尾數(shù)原始數(shù)據(jù)
unsigned long ulMantissa = ( nMem & 0x7FFFFFL );
// 和第23位是1的數(shù)據(jù)0x800000進(jìn)行按位或,可補(bǔ)齊省去的最高位1
ulMantissa |= 0x800000L;
// 以16進(jìn)制整數(shù)方式輸出尾數(shù)數(shù)據(jù)
cout << "尾數(shù):0x" << setbase( 16 ) << setfill( '0' ) << setw( 8 );
cout << setiosflags( ios_base::uppercase ) << ulMantissa << endl;
// 完全基于整型算法來(lái)實(shí)現(xiàn)二進(jìn)制小數(shù)到整數(shù)的轉(zhuǎn)換極其復(fù)雜,
// 這里借用double簡(jiǎn)單實(shí)現(xiàn),僅說(shuō)明其理論。詳細(xì)算法見(jiàn)說(shuō)明。
// 計(jì)算出浮點(diǎn)數(shù)的整數(shù)部分,用雙精度型表示
double dInteger = 0;
// 指數(shù)大于或等于0就代表尾數(shù)中存在整數(shù)部分
if ( cExponent >= 0 )
{
// 如果指數(shù)大于23,則說(shuō)明整數(shù)部分以科學(xué)計(jì)數(shù)法表示
if ( cExponent > 23 )
{
// dCurBit用來(lái)計(jì)算和存儲(chǔ)循環(huán)中對(duì)于每一位的位階數(shù)
double dCurBit = 1.0;
// 循環(huán)所有位,計(jì)算科學(xué)計(jì)數(shù)法中的小數(shù)的十進(jìn)制形式
for ( int nBitIdx = 0; nBitIdx < 24; nBitIdx++ )
{
// 將整數(shù)部分左移當(dāng)前位加9,將當(dāng)前位置于最高位;
// 再右移31位可得當(dāng)前位,再用當(dāng)前位階乘以該位,
// 即得累加數(shù)。累加計(jì)入二進(jìn)制小數(shù)。
dInteger += dCurBit * ( ( ulMantissa <<
( 8 + nBitIdx ) ) >> 31 );
// 由當(dāng)前位階除以2得到下次位階。
dCurBit /= 2;
}
// 將二進(jìn)制表示的科學(xué)計(jì)數(shù)法轉(zhuǎn)換為10進(jìn)制
dInteger *= pow( 2.0, (double)cExponent );
}
else
{
// 將尾數(shù)右移小數(shù)部分的長(zhǎng)度,即可得到整數(shù)部分
int nRightShift = 0;
// 如果指數(shù)小于23,則說(shuō)明存在小數(shù)部分,需要右移
if ( cExponent < 23 )
{
// 尾數(shù)的原始長(zhǎng)度減去指數(shù)即為小數(shù)部分長(zhǎng)度
nRightShift = 23 - cExponent;
}
// 將尾數(shù)數(shù)據(jù)右移小數(shù)部分的長(zhǎng)度,可得到整數(shù)部分。
dInteger = (double)( ulMantissa >> nRightShift );
}
}
// 以10進(jìn)制無(wú)符號(hào)整數(shù)方式輸出整數(shù)部分
cout << "整數(shù)部分:" << setbase( 10 ) << setprecision( 8 );
cout << dInteger << endl;
// 計(jì)算出浮點(diǎn)數(shù)的小數(shù)部分,用無(wú)符號(hào)長(zhǎng)整型表示
double dDecimal = 0;
// 如果指數(shù)小于23,則尾數(shù)中含小數(shù)部分
if ( cExponent < 23 )
{
// 指數(shù)與23的差就是小數(shù)部分的長(zhǎng)度,詳見(jiàn)整數(shù)部分的處理。
// 將所有位均為1的32位整數(shù)右移32減小數(shù)部分長(zhǎng)度,即指數(shù)加9,
// 可得到用于取得小數(shù)部分的mask數(shù)。
unsigned long ulDecimalMask = 0xFFFFFFFF;
// 如果指數(shù)大于或等于0就代表尾數(shù)中存在整數(shù)部分
if ( cExponent >= 0 )
{
ulDecimalMask >>= ( 9 + cExponent );
}
else
{
// 如果是純小數(shù),則必須恢復(fù)規(guī)格化時(shí)刪掉的整數(shù)1
dDecimal = 1.0;
}
// 將尾數(shù)和mask數(shù)進(jìn)行按位與,可得到小數(shù)部分
// 二進(jìn)制小數(shù)用無(wú)符號(hào)長(zhǎng)整型表示
unsigned long ulDecimal = ulMantissa & ulDecimalMask;
// dCurBit用來(lái)計(jì)算和存儲(chǔ)循環(huán)中對(duì)于每一位的位階數(shù)
double dCurBit = 0.5;
// 循環(huán)所有位,計(jì)算科學(xué)計(jì)數(shù)法中的小數(shù)的十進(jìn)制形式
for ( int nBitIdx = 1; nBitIdx < 24; nBitIdx++ )
{
// 將二進(jìn)制小數(shù)左移當(dāng)前位加9,將當(dāng)前位置于最高位;
// 再右移31位可得當(dāng)前位。
// 用dCurBit乘以當(dāng)前位,即得累加數(shù)。累加計(jì)入dDecimal。
dDecimal += dCurBit * ( ( ulDecimal <<
( 8 + nBitIdx ) ) >> 31 );
// 由當(dāng)前位階除以2得到下次位階。
dCurBit /= 2;
}
// 將二進(jìn)制表示的科學(xué)計(jì)數(shù)法轉(zhuǎn)換為10進(jìn)制
dDecimal *= pow( 2.0, (double)cExponent );
}
// 以10進(jìn)制無(wú)符號(hào)整數(shù)方式輸出小數(shù)部分
cout << "小數(shù)部分:" << setbase( 10 ) << setprecision( 8 );
cout << dDecimal << endl;
}
else
{
cout << "浮點(diǎn)數(shù)為0,無(wú)分析意義" << endl;
}
cout << "-----------------------" << endl;
cout << "分析完畢,程序退出。" << endl << endl;
////////////不可改動(dòng)//////////////
system( "pause" );
// _CrtDumpMemoryLeaks();
return 0;
////////////不可改動(dòng)//////////////
}
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。