浮點型的原理介紹及在記憶體中的儲存形式
浮點型的原理介紹及在記憶體中的儲存形式
C語言提供了浮點資料型別,單精度浮點數float和雙精度浮點數double。浮點數屬於不精確的資料型別,本文將通過float型別的原理和在記憶體中的儲存形式來介紹浮點型不精確的原因。以float型別為例,來展示C語言中浮點型的神祕之處。
float型別介紹
float是baiC語言的基本資料型別中的一種,表示單精度浮點數。C語言規定單精度浮點型在記憶體佔用4個位元組,精度為7位,取值範圍為:3.4*10^-38 ~3.4*10^38或者-(3.4*10^-38 ~3.4*10^38)。依據IEEE規定 :float在儲存中都分為三個部分:
1、符號位(Sign) : 0代表正,1代表為負
2、指數位(Exponent):用於儲存科學計數法中的指數資料,並且採用移位儲存
3、尾數部分(Mantissa):尾數部分
其中float和double的儲存方式如下圖所示:
資料型別 | 符號位 | 指數 | 尾數 |
float | 1bit | 8bit | 23bit |
double | 1bit | 11bit | 52bit |
float在記憶體中的儲存規則
計算機只能識別二進位制,所以float型別在記憶體中也是通過二進位制儲存的。因此float型別需要轉換為二進位制形態,轉換步驟如下:
1)先將這個實數的絕對值化為二進位制格式。
2)將這個二進位制格式實數的小數點左移或右移n位,直到小數點移動到第一個有效數字的右邊。
3)從小數點右邊第一位開始數出二十三位數字放入第22到第0位。
4)如果實數是正的,則在第31位放入“0”,否則放入“1”。
5)如果n 是左移得到的,說明指數是正的,第30位放入“1”。如果n是右移得到的或n=0,則第30位放入“0”。
6)如果n是左移得到的,則將n減去1後化為二進位制,並在左邊加“0”補足七位,放入第29到第23位。如果n是右移得到的或n=0,則將n化為二進位制後在左邊加“0”補足七位,再各位求反,再放入第29到第23位
注意:將絕對值轉換為二進位制之後,小數點左移n位,n即為該數的指數,指數為階碼為n+127。這其實就是第5和6步驟運算得到的結果。
示例:
通過上述轉換規則計算兩個浮點數的二進位制。數字 13.6 和 資料 1.3
符號位比較簡單,儲存的是正數那麼符號數就是0。如果是負數,則為1。
下面以13.6為例說明指數與尾數的表示方法。首先,我們取出13.6的整數部為13。對其使用短除法(對該數除以2,直至不能再除的一種方法)結果如下:
將各餘數自下而上排列,則得到了13的二進位制表示。之後,取出13.6的小數部分為0.6對其每次乘2取出整數留下小數,直至得到1。結果如下:
這樣我們就得到了13.6的二進位制表示。為1101.100110011001....... 之後我們需將小數點移動至整數部只有一位。移動後得到1.101100110011001.......。在此我們將小數點移動了三位。因而三即是該數的指數。而階碼則為指數+127(加127是C語言的內在邏輯,在此我們並不深究。)因而我們得到了13.6的指數,為130。二進位制表示10000010。最後的尾數,將原先得到的無線循壞的二進位制取前23位即可,如果第24位為0直接捨棄,如果第24為1,則第23位加1。(float型別的尾數有23位,對於沒有迴圈的數字,在後補齊0即可)。因此我們得到13.6的儲存資料:
0 | 1000 0010 | 101 1001 1001 1001 1001 1010 | 1001......(第24位為1,所以尾數加1變成10,其他的捨棄) |
同理得到1.3在記憶體的儲存資料如下:
0 | 0111 1111 | 010 0110 0110 0110 0110 0110 | 0110......(第24為0,後邊的直接捨棄) |
接下來通過C程式除錯,來檢視浮點數在記憶體中的儲存形式:
#include <stdio.h> int main() { float a = 13.6; printf("%p\n", &a); printf("%.9f\n", a); return 0; }
對上述程式進行除錯,根據打印出變數a的地址在記憶體中找出該浮點數的儲存形式:
可以看出變數a在記憶體中的儲存形式為 41 59 99 9a。(小端儲存:較高的有效位元組存放在較高的儲存器地址,較低的有效位元組存放在較低的儲存器地址)將上述十六進位制轉換為二進位制0100 0001010110011001100110011010 。該結果與上文中我們計算出的資料一致。對於數字 1.3 使用同樣的方式驗證。
從記憶體讀取float型別
從上文已經知道了浮點型在記憶體中如何儲存,那是如何讀取呢,讀取之後為什麼會出現精度丟失的現象。本小節來演示下float型別的讀取。規則如下:
1)將第22位到第0位的二進位制數寫出來,在最左邊補一位“1”,得到二十四位有效數字。將小數點點在最左邊那個“1”的右邊。
2)取出第29到第23位所表示的值n。當30位是“0”時將n各位求反。當30位是“1”時將n增1。
3)將小數點左移n位(當30位是“0”時)或右移n位(當30位是“1”時),得到一個二進位制表示的實數。
4)將這個二進位制實數化為十進位制,並根據第31位是“0”還是“1”加上正號或負號即可
注意:讀取和儲存是相反的過程,階碼也可以通過當前值 -127 來進行轉換。
示例:
從上文中,我們已經從記憶體中獲取到 13.6 的儲存形式為0100 0001 0101 1001 1001 1001 1001 1010 現在通過步驟進行該資料的讀取:
1、獲取尾數得到24位有效數字 1.101 1001 1001 1001 1001 1010
2、00 0001 0 值為2,第31位是1,所以將n+1。得到n為00 0001 1
3、將小數點左移3位得到二進位制實數1101. 1001 1001 1001 1001 1010
5、將小數點前後分別計算十進位制數字,1101轉換為十進位制為13。0. 1001 1001 1001 1001 1010 轉為10進位制數字如下所示:
得到最終實數為 13.6000003814697266。
所以當獲取數字時,如果小數位的有效數字小於7位時,該數字是精確的,超過7位資料就出現了不精確位,所以說浮點型是不精確的資料型別。
同樣的辦法,我們可以獲取到實數 1.3 經過轉換後得到的實數為 1.2999999523162842
我們通過這兩個例子可以得出結論,當讀取資料時,根據需要獲取的精度,之後的資料進行四捨五入之後捨棄。
結論:
經過對程式的除錯,我們已經驗證了浮點型不精確的原因以及float有效精度為6位。所以在使用浮點型時應特別注意精度丟失現象,同樣的double型別有效精度為15位。計算方式和float類似,有興趣的同學可以自行驗證。