浮點型的原理介紹及在內存中的存儲形式
C語言提供了浮點數據類型,單精度浮點數float和雙精度浮點數double。浮點數屬於不精確的數據類型,本文將通過float類型的原理和在內存中的存儲形式來介紹浮點型不精確的原因。以float類型為例,來展示C語言中浮點型的神秘之處。
float類型介紹
float是C語言的基本數據類型中的一種,表示單精度浮點數。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
4、將小數點前后分別計算十進制數字,1101轉換為十進制為13。0. 1001 1001 1001 1001 1010 轉為10進制數字如下所示:
得到最終實數為 13.6000003814697266。
所以當獲取數字時,如果小數位的有效數字小於7位時,該數字是精確的,超過7位數據就出現了不精確位,所以說浮點型是不精確的數據類型。
同樣的辦法,我們可以獲取到實數 1.3 經過轉換后得到的實數為 1.2999999523162842
我們通過這兩個例子可以得出結論,當讀取數據時,根據需要獲取的精度,之后的數據進行四舍五入之后舍棄。
結論:
經過對程序的調試,我們已經驗證了浮點型不精確的原因以及float有效精度為6位。所以在使用浮點型時應特別注意精度丟失現象,同樣的double類型有效精度為15位。計算方式和float類似,有興趣的同學可以自行驗證。
本文參考:https://blog.csdn.net/zcliatb/article/details/41078595