asp 强制转换浮点数值_C/C++中浮点数的编码存储
浮點(diǎn)數(shù)也稱做實(shí)型數(shù)據(jù)(實(shí)數(shù)),形式上就是數(shù)學(xué)中的小數(shù)。浮點(diǎn)型數(shù)據(jù)有兩種表達(dá)方式: 一種是用數(shù)字和小數(shù)點(diǎn)表示的,如123.456; 另一種是用指數(shù)方式表示,如1.2e-6 或1.2E-6(1.2*10-6)。
在計(jì)算機(jī)中實(shí)數(shù)是如何存儲的呢?主要分為定點(diǎn)實(shí)數(shù)存儲方式和浮點(diǎn)實(shí)數(shù)存儲方式這兩種。所謂定點(diǎn)實(shí)數(shù),就是約定整數(shù)位和小數(shù)位的長度,比如用4字節(jié)存儲實(shí)數(shù),我們可以讓高兩個(gè)字節(jié)存放整數(shù)部分,低兩個(gè)字節(jié)存儲小數(shù)部分。這樣的好處是計(jì)算的效率高,缺點(diǎn)是如果我們想存儲65536.5,由于整數(shù)的表達(dá)范圍超過了兩個(gè)字節(jié),用定點(diǎn)存儲的方式就無法存儲了。
對應(yīng)地,也有浮點(diǎn)實(shí)數(shù)存儲方式,就是用一部分二進(jìn)制位存放小數(shù)點(diǎn)的位置信息,我們可以稱之為”指數(shù)域”,其他的數(shù)據(jù)位用來存儲沒有小數(shù)點(diǎn)的數(shù)據(jù)和符號,我們可以稱之為“數(shù)據(jù)域”、“符號域”。在訪問時(shí)取得指數(shù)域,與數(shù)據(jù)域運(yùn)算后得到真值,如67.625,利用浮點(diǎn)實(shí)數(shù)存儲方式,數(shù)據(jù)域可以記錄為67625,小數(shù)點(diǎn)的位置可以記錄為10的-3次方。后來引進(jìn)了浮點(diǎn)協(xié)處理器(FPU),專門負(fù)責(zé)對浮點(diǎn)數(shù)的處理,使得對處理實(shí)數(shù)的效率大大提高,于是浮點(diǎn)實(shí)數(shù)存儲方式也就普及開來,成為了現(xiàn)在主流的實(shí)數(shù)存儲方式。
在C/C++中,使用浮點(diǎn)方式存儲實(shí)數(shù),用兩種數(shù)據(jù)類型來保存浮點(diǎn)數(shù):float(單精度)和double(雙精度)。Float在內(nèi)存中占用4字節(jié)空間,double在內(nèi)存中占用8個(gè)字節(jié)空間。Double類型比float類型精度更高。這兩種數(shù)據(jù)在內(nèi)存中都是以十六進(jìn)制方式存儲,但與整型數(shù)據(jù)有所不同。
整型數(shù)據(jù)是將十進(jìn)制直接轉(zhuǎn)換成二進(jìn)制保存在內(nèi)存中,以十六進(jìn)制方式顯示。而浮點(diǎn)類型不是將一個(gè)浮點(diǎn)小數(shù)直接轉(zhuǎn)換成二進(jìn)制保存,而是將浮點(diǎn)小數(shù)轉(zhuǎn)換成二進(jìn)制碼 后 重新編碼,再進(jìn)行存儲。C/c++的浮點(diǎn)數(shù)是有符號的。
在C/C++中,將浮點(diǎn)數(shù)強(qiáng)制轉(zhuǎn)換成整數(shù)時(shí),不會采用數(shù)學(xué)上的四舍五入方式,而是舍棄掉小數(shù)部分。
浮點(diǎn)數(shù)的操作不會用到通用寄存器,而是用浮點(diǎn)協(xié)處理器的浮點(diǎn)寄存器。
浮點(diǎn)數(shù)的編碼方式
浮點(diǎn)編碼轉(zhuǎn)換采用的是IEEE規(guī)定的編碼標(biāo)準(zhǔn),float和double這兩種類型數(shù)據(jù)的轉(zhuǎn)換原理相同,但由于表示的范圍不一樣,編碼方式有些區(qū)別。IEEE規(guī)定的浮點(diǎn)數(shù)編碼會將一個(gè)浮點(diǎn)數(shù)轉(zhuǎn)換為二進(jìn)制數(shù)。以科學(xué)計(jì)數(shù)法劃分,將浮點(diǎn)數(shù)拆分為3個(gè)部分:符號、指數(shù)和尾數(shù)。
1、float類型的IEEE編碼
Float類型在內(nèi)存中占4個(gè)字節(jié)(32位)。最高位表示符號:在剩余的31位中,從右到左取8位 用于表示指數(shù),其余用于表示尾數(shù)。如圖2-2所示:
float類型的IEEE編碼
1)在進(jìn)行二進(jìn)制轉(zhuǎn)換前,需要對單精度(float)浮點(diǎn)數(shù)進(jìn)行科學(xué)計(jì)數(shù)法轉(zhuǎn)換。例如,將float類型的12.25f(f表示為float單精度類型)轉(zhuǎn)換為IEEE編碼,需要將12.25f轉(zhuǎn)換成對應(yīng)的二進(jìn)制數(shù)1100.01,整數(shù)部分為1100,小數(shù)部分為01。小數(shù)點(diǎn)向左移動,每移動1次指數(shù)加1,移動到除了符號位的最高位1處,停止移動。這里移動3次。對12.25f進(jìn)行科學(xué)記數(shù)法轉(zhuǎn)換后的二進(jìn)制部分為1.10001,指數(shù)部分為3。在IEEE編碼中,由于在二進(jìn)制情況下,最高位始終為1,為一個(gè)恒定值,故將其忽略不計(jì)。這里是一個(gè)正數(shù),所以符號位添0。
12.25f經(jīng)過IEEE轉(zhuǎn)換后各位的情況:
符號位:0
指數(shù)為:十進(jìn)制 3+127,轉(zhuǎn)換為二進(jìn)制10000010
尾數(shù)位:10001 000000000000000000(當(dāng)不足23位時(shí),低位補(bǔ)0填充)
由于尾數(shù)位中最高位1是恒定值,故省略不計(jì),只要在轉(zhuǎn)換回十進(jìn)制時(shí)加1即可。為什么指數(shù)位要加127呢?由于指數(shù)可能出現(xiàn)負(fù)數(shù),十進(jìn)制127 可表示二進(jìn)制數(shù) 01111111。IEEE編碼方式規(guī)定,當(dāng)指數(shù)域小于0111111時(shí)為一個(gè)負(fù)數(shù),反之為正數(shù),因此 指數(shù)域加上十進(jìn)制數(shù) 127 表示正數(shù)。
12.25f轉(zhuǎn)換后的IEEE編碼按二進(jìn)制拼接為 0 10000010 10001000000000000000000。轉(zhuǎn)換后成十六進(jìn)制數(shù) 0x41440000,內(nèi)存中以小端進(jìn)行存儲,故為 00 00 44 41。分析結(jié)果如圖所示:
2)上面演示了符號位為正,指數(shù)為也為正的情況。那么什么情況下指數(shù)位可以為負(fù)呢?根據(jù)科學(xué)記數(shù)法,小數(shù)點(diǎn)向整數(shù)部分移動時(shí),指數(shù)做加法。相反,小數(shù)點(diǎn)向小數(shù)部分移動時(shí),指數(shù)需要以0起始做減法。浮點(diǎn)數(shù) -0.125f轉(zhuǎn)換成IEEE編碼后,將會是一個(gè)符號位為1,指數(shù)部分為負(fù)的小數(shù)。-0.125f經(jīng)轉(zhuǎn)換后二進(jìn)制部分為0.001,用科學(xué)記數(shù)法為1.0,指數(shù)為-3。
-0.125f 經(jīng)過IEEE轉(zhuǎn)碼后各位的情況為:
符號位:1
指數(shù)位:十進(jìn)制127+(-3),轉(zhuǎn)換為二進(jìn)制是 01111100,如果不足8位,則高位補(bǔ)0
尾數(shù)位:0000000000000000000000000
-0.125f經(jīng)轉(zhuǎn)換后的IEEE編碼二進(jìn)制拼接為 1 01111100 0000000000000000000000000。轉(zhuǎn)換后成十六進(jìn)制為 0xBE000000,內(nèi)存中顯示為 00 00 00 BE。分析結(jié)果如圖所示:
3)上面的兩個(gè)浮點(diǎn)小數(shù)部分轉(zhuǎn)換為二進(jìn)制時(shí)都是有窮的,如果小數(shù)部分轉(zhuǎn)換為二進(jìn)制時(shí)得到一個(gè)無窮值,則會根據(jù)尾數(shù)部分的長度舍棄多余的部分。單精度浮點(diǎn)數(shù)1.3f,小數(shù)部分轉(zhuǎn)換為二進(jìn)制就會產(chǎn)生無窮值,依次轉(zhuǎn)換為0.3、0.6、1.2、0.4、0.8、1.6、1.2、0.4、0.8...,轉(zhuǎn)換后得到的二進(jìn)制數(shù)位1.01001100110011001100110,到第23為時(shí)終止,尾數(shù)部分無法再保存。
1.3f經(jīng)過IEEE轉(zhuǎn)換后各位的情況:
符號位:0
指數(shù)位:十進(jìn)制0+127,轉(zhuǎn)換二進(jìn)制01111111
尾數(shù)位:01001100110011001100110
1.3f 轉(zhuǎn)換后的IEEE編碼二進(jìn)制拼接為 0 01111111 01001100110011001100110。轉(zhuǎn)換成十六進(jìn)制數(shù)位 0x3fa66666,內(nèi)存中顯示為 66 66 a6 3f。由于在轉(zhuǎn)換二進(jìn)制過程中產(chǎn)生了無窮值,舍棄了部分位數(shù),所以進(jìn)行IEEE編碼轉(zhuǎn)換后得到的是一個(gè)近似值,存在一定的誤差。再將這個(gè)IEEE編碼值轉(zhuǎn)換成十進(jìn)制小數(shù),得到的值為1.2516582,四舍五入后為1.3.這也解釋了為什么C++ 在比較浮點(diǎn)數(shù)值是否為0時(shí),要做一個(gè)區(qū)間而不是直接進(jìn)行等值比較。如:
float fTemp = 0.0001f; // 精確范圍
if (fFloat >= -fTemp && fFloat <= fTemp)
{
fTemp等于0
}
2.double類型的IEEE編碼
前文講解了單精度浮點(diǎn)類型的IEEE編碼。Double類型和float類型大同小異,只是double類型表示的范圍更大,占用空間更多,精度更準(zhǔn)。
Double 類型占8字節(jié)的內(nèi)存空間,同樣最高位也用于表示符號,指數(shù)位占11位,剩余的52位用于表示尾數(shù)。
在float中,指數(shù)位范圍用8位表示,加127后用于判斷指數(shù)符號。在double中,由于擴(kuò)大了精度,因此指數(shù)范圍使用11位正數(shù)來表示,加上1023來用于指數(shù)符號判斷。
Double 類型的IEEE編碼轉(zhuǎn)換過程和float一樣。
3.浮點(diǎn)數(shù)指令
浮點(diǎn)數(shù)的操作指令和普通數(shù)據(jù)類型不同,浮點(diǎn)數(shù)操作是通過浮點(diǎn)寄存器來實(shí)現(xiàn)的,而普通數(shù)據(jù)使用的是通用寄存器,如eax、edx、ebx等。
浮點(diǎn)寄存器是通過棧結(jié)構(gòu)來實(shí)現(xiàn)的,由ST(0)~ST(7)共8個(gè)棧空間組成,每個(gè)浮點(diǎn)寄存器占8個(gè)字節(jié)。每次使用浮點(diǎn)寄存器都是先使用St(0),而不能越過ST(0)直接使用ST(1)。浮點(diǎn)寄存器的使用就是壓棧、出棧的過程。當(dāng)ST(0)存在數(shù)據(jù)時(shí),執(zhí)行壓棧操作,ST(0)中的數(shù)據(jù)將進(jìn)入到ST(1)中,如無出棧操作,將順序地向下壓棧,直到將浮點(diǎn)寄存器占滿。常用浮點(diǎn)數(shù)指令如下所示:IN 表示操作數(shù) 入棧。OUT表示操作數(shù)出棧。
常用浮點(diǎn)數(shù)指令
其他運(yùn)算指令和普通指令類似,只需在前面加F就行,如 FSUB和FSUBP等。
在使用浮點(diǎn)指令時(shí),都要先利 用ST(0)進(jìn)行運(yùn)算。當(dāng)ST(0)中有值時(shí),便會將ST(0)中的數(shù)據(jù)順序向下存放到ST(1)中,然后再將數(shù)據(jù)放入ST(0)中。如果再次操作ST(0),則會先將ST(1)中的數(shù)據(jù)放入ST(2)中,然后將ST(0)中的數(shù)據(jù)放入到ST(1)中,最后才將新的數(shù)據(jù)存放到ST(0)。以此類推,在八個(gè)浮點(diǎn)寄存器都有值的情況下繼續(xù)向ST(0)存放數(shù)據(jù),這時(shí)會丟棄ST(7)中的數(shù)據(jù)信息。
1)下面通過一個(gè)簡單的例子來了解各個(gè)指令的使用流程:
// 浮點(diǎn)數(shù)使用
float fFloat = (float)argc;
00401028 fild dword ptr [ebp+8]
//將ebp+8處的整型數(shù)據(jù)轉(zhuǎn)換成浮點(diǎn)型,并放入ST(0)中,對應(yīng)變量 argc
0040102B fst dword ptr [ebp-4]
//從ST(0) 中取出數(shù)據(jù)以浮點(diǎn)編碼的方式放入地址ebp-4 中,對應(yīng)變量 fFloat
printf("%f", fFloat);
0040102E sub esp,8
//這里對esp減 8 操作是由于浮點(diǎn)數(shù)作為變參函數(shù)的參數(shù)時(shí)需要轉(zhuǎn)換成雙精度浮點(diǎn)值,
//這步操作是 提前準(zhǔn)備8字節(jié)的棧空間,以便存放double數(shù)據(jù)。
00401031 fstp qword ptr [esp]
//將ST(0) 中的數(shù)據(jù)傳入esp中,并彈出ST(0)。
00401034 push offset string "%f" (00426020)
00401039 call printf (00401420)
0040103E add esp,0Ch
argc = (int)fFloat;
//將 float類型數(shù)據(jù)轉(zhuǎn)換成int型
00401041 fld dword ptr [ebp-4]
//將ebp-4處的數(shù)據(jù)以浮點(diǎn)型壓入ST(0)中。
00401044 call __ftol (00401588)
//調(diào)用函數(shù) __ftol 進(jìn)行浮點(diǎn)數(shù)轉(zhuǎn)換, __ftol的實(shí)現(xiàn)見下文。
00401049 mov dword ptr [ebp+8],eax
printf("%d", argc);
0040104C mov eax,dword ptr [ebp+8]
0040104F push eax
00401050 push offset string "%d" (0042601c)
00401055 call printf (00401420)
0040105A add esp,8
從上面示例中可以發(fā)現(xiàn),float類型的浮點(diǎn)數(shù)雖然占4個(gè)字節(jié),但都是以8個(gè)字節(jié)(qword)方式進(jìn)行處理。當(dāng)浮點(diǎn)數(shù)作為參數(shù)時(shí),并不能直接壓棧。Push 指令只能傳入4字節(jié)數(shù)據(jù)到棧中,這樣會丟失4字節(jié)數(shù)據(jù)。這就是為什么使用printf函數(shù)以整型方式輸出浮點(diǎn)數(shù)會產(chǎn)生錯(cuò)誤的原因。Printf以整數(shù)方式輸出時(shí),將對應(yīng)參數(shù)作為4字節(jié)數(shù)據(jù),按補(bǔ)碼方式解釋。而真正壓入的參數(shù)為浮點(diǎn)類型時(shí),數(shù)據(jù)長度為8字節(jié),需要按浮點(diǎn)編碼解釋。
2)浮點(diǎn)數(shù)作為返回值的情況也是如此,同樣需要傳遞8字節(jié)數(shù)據(jù),代碼如下所示:
float fFloat;
fFloat = GetFloat();
00401058 call @ILT+5(_GetFloat) (0040100a)
//調(diào)用GetFloat函數(shù)
0040105D fst dword ptr [ebp-4]
//由于浮點(diǎn)數(shù)需要特殊處理,浮點(diǎn)數(shù)占8個(gè)字節(jié),無法使用EAX進(jìn)行傳遞
//因此使用 浮點(diǎn)寄存器 ST(0) 作為返回值
printf("%f", fFloat);
00401060 sub esp,8
00401063 fstp qword ptr [esp]
00401066 push offset string "%f" (00426020)
0040106B call printf (00401420)
00401070 add esp,0Ch
//GetFloat 函數(shù)
float GetFloat()
{
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,40h
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-40h]
0040101C mov ecx,10h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
return 12.25f;
00401028 fld dword ptr [string "%d" (0042601c)]
//將浮點(diǎn)數(shù)保存在 ST(0)中,在返回值為浮點(diǎn)數(shù)的情況下,無法使用EAX
//使用ST(0)作為返回值進(jìn)行傳遞。
}
0040102E pop edi
0040102F pop esi
00401030 pop ebx
00401031 mov esp,ebp
00401033 pop ebp
00401034 ret
3)在上面代碼中,float型數(shù)據(jù)被強(qiáng)制轉(zhuǎn)換為int型,編譯器通過了__ftol函數(shù)實(shí)現(xiàn)了轉(zhuǎn)換過程,如下面所示:
__ftol:
00401588 push ebp
00401589 mov ebp,esp
0040158B add esp,0FFFFFFF4h
//保存環(huán)境,預(yù)留語句變量空間
0040158E wait
0040158F fnstcw word ptr [ebp-2]
00401592 wait
00401593 mov ax,word ptr [ebp-2]
00401597 or ah,0Ch
0040159A mov word ptr [ebp-4],ax
0040159E fldcw word ptr [ebp-4]
//浮點(diǎn)異常檢查、CPU與FPU的同步工作
004015A1 fistp qword ptr [ebp-0Ch]
//從ST(0)中取出8字節(jié)數(shù)據(jù)轉(zhuǎn)換成整型并存入到ebp-0ch中
//從ST(0)中彈出
004015A4 fldcw word ptr [ebp-2]
004015A7 mov eax,dword ptr [ebp-0Ch]
//使用eax保存整型數(shù)據(jù)的低4字節(jié),用于返回
004015AA mov edx,dword ptr [ebp-8]
//使用edx保存整型數(shù)據(jù)的高4字節(jié),用于返回
004015AD leave
//釋放棧空間
004015AE ret
004015AF int 3
————————摘自《C++反匯編與逆向分析技術(shù)揭秘》
總結(jié)
以上是生活随笔為你收集整理的asp 强制转换浮点数值_C/C++中浮点数的编码存储的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海南橡胶机器人成本_「图说」海垦看点:海
- 下一篇: c++ long 转 short_C精品