浮點數類型在內存當中是如何存儲的


問題的拋出:

版權聲明:本文為博主原創文章,轉載請附上原文出處鏈接和本聲明。2019-10-03,00:56:39。
作者By-----溺心與沉浮----博客園

  為什么兩個浮點數相減時,有時出乎我們意料之外的值呢?例如3.1415927 - 3.1415926 = 0.0000002?(例子我隨便舉得,大家不要在乎這個,例子中這個值我也沒有碰到過,但我相信大家在做浮點數運算時,肯定有這種類似的情況)這涉及到精確問題。

1/3用分數可以來很好的表示,可是如果不允許用分數表示呢?如何保證數盡可能的等於1/3呢?相信大家都知道,0.3......小數點后面3的數量越多,表示的就更加靠近。

  就如十進制系統中不能准確表示出1/3!同樣二進制系統也無法准確表示1/10。這也就解釋了為什么浮點型減法出現了“減不盡”的精度丟失問題。

概述:

  float和double在存儲方式上都是遵從IEEE的規范的,float的存儲方式如下圖所示:

 

 

 

 

  double的存儲方式如下圖所示:

步驟:

   將一個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位。

首先我們需要搞清楚下面兩個問題:
  (1)十進制整數如何轉化為二進制數
  算法很簡單,舉個例子,11表示成二進制數:
        11 / 2 余 1
        5 / 2 余 1
        2 / 2 余 0
        1 / 2 余 1
         0結束 11進制表示為(從下往上)1011
  這里提一點,只要遇到除以后的結果為0了就結束了,大家想想,所有的整數除以2是不是一定能夠最終得到0呢。換句話說,所有的整數轉變為二進制數的算法會不會無線循環下去呢?絕對不會,整數永遠可以用二進制精確表示,但小數不一定了。
  (2)十進制小數如何轉化為二進制數
  算法是乘以2直到沒有了小數為止。舉個例子,0.9表示成二進制數
        0.9 × 2 = 1.8 取整數部分 1
        0.8(1.8的小數部分) × 2 = 1.6 取整數部分 1
        0.6 × 2 = 1.2 取整數部分 1
        0.2 × 2 = 0.4 取整數部分 0
        0.4 × 2 = 0.8 取整數部分 0
        0.8 × 2 = 1.6 取整數部分 1
        0.6 × 2 = 1.2 取整數部分 1
        0.2 × 2 = 0.4 取整數部分 0
        0.4 × 2 = 0.8 取整數部分 0
          ...... 0.9二進制表示為(從上往下):11100110011001100......
  注意:上面的計算過程循環了,相信你也發現了,也就是說本例子中0.9×2永遠不可能消滅小數部分,這樣算法講無限循環下去。很顯然,小數的二進制表示有時是不可能精確的。其實道理很簡單,十進制系統中能不能准確表示處1/3呢?同樣二進制系統也無法准確表示1/10。這也就解釋了為什么浮點型減法出現了“減不盡”的精度丟失問題。

舉例:

  以8.25舉例,看看8.23在計算中是如何存儲的

  (1)首先將整數部分8轉換成2進制

      8  /  2    余  0

      4  /  2    余  0

      2  /  2    余  0

      1  /  2    余  1

   

   (2)小數部分0.25轉換成二進制

      0.25 × 2 = 0.5  取整數部分  0

      0.5   × 2 = 1.0  取整數部分  1

   

   (3)整數部分與小數部分都已轉換成了二進制,那么8.25的二進制表示為1000.01,雖然是可以這么表示了,但是計算機認識嗎?所有的內容(數,字母,漢字,符號)在計算機世界里只有2種形態,0和1,因此這不是我們最終要展現的數!

   (4)8.25---->>>1000.01用二進制的科學計數法表示(還記得十進制的科學計數法嗎?換個進制來使用)1.00001 × 2的3次方,指數為3。

    (5)首先8.25我們表現形式是正數,那么31位中填0(記得前面說的有符號數與無符號數的博文嗎?),0x7FFFFFFF, 0x80000000這兩個數便是有符號數的一個界限,所以在有符號數中最高位為1,代表負,最高位為0,代表正!

    但0x80000000它一定代表着負數嗎?不是,在無符號數中,最高位為1,依然是正數。怎么區分,看使用者如何定義。這里我不做介紹了!

    正數,符號位,我們在第31位中填0,

    指數部分,我們的n是左移得到的,說明我們的指數為正,因此第30位我們填1,然后我們將指數n減1,得到2,它的二進制是10,並在左邊添0,從第29位開始到第23位,我們湊夠7位,因此指數部分是10000010

    

     (6)22 - 0尾數部分怎么填呢?我們8.25轉換成二進制不是1000.01也即1.00001 × 2的3次方嗎?我們取科學計數法小數點后面的數,從第22位開始,全部往里扔,后面不足全部補0

       

     (7)最終的表示:

       

     整理一下,我們得到:0100 0001 0000 0100 0000 0000 0000 0000,這便是我們最終需要的二進制表示形式,轉換成16進制為:0x41040000,我們用編譯器查看一下

 1 // xiaoyu1.cpp : Defines the entry point for the console application.  2 //  3 
 4 #include "stdafx.h"
 5 
 6 void Function()  7 {  8     float a = 8.25f;  9 } 10 
11 int main(int argc, char* argv[]) 12 { 13  Function(); 14     return 0; 15 }

 

   

 1 6: void Function()  2 7: {  3 00401020   push ebp  4 00401021   mov ebp,esp  5 00401023   sub esp,44h  6 00401026   push ebx  7 00401027   push esi  8 00401028   push edi  9 00401029   lea edi,[ebp-44h] 10 0040102C   mov ecx,11h 11 00401031   mov eax,0CCCCCCCCh 12 00401036   rep stos dword ptr [edi] 13 8:        float a = 8.25f; 14 00401038   mov         dword ptr [ebp-4],41040000h 15 9: } 16 0040103F   pop edi 17 00401040   pop esi 18 00401041   pop ebx 19 00401042   mov esp,ebp 20 00401044   pop ebp 21 00401045   ret

 通過反匯編查看得知,壓入棧中的8.25,是0x41040000

例子2

  既然8.25的會了,那么-8.25呢?如何表示呢?

  很簡單,首先將-8.25的絕對值用二進制表示出來就行了!跟8.25表示形式一模一樣,只不過第31位為1,因為是負數,所以-8.25的最終二進制表示為0xC1040000,我們反匯編查看一下。看是不是這個呢?

                                   

  經過反匯編觀察,發現與我們計算結果一致吧~~~~~ 

例子3

  如何將一個只有小數部分的數用二進制來表示呢?(這也是一個難點,與有整數部分不一樣的是,只有小數部分的數,通常它的科學計數法表示中指數為負),我們還沒有探討過指數為負的情況!

  我以0.25為例!

  0.25的二進制表示為:

      0.25  ×   2   =  0.5     取整數部分為  0

      0.5    ×   2   =  0.5     取整數部分為  1

   (1)0.25用二進制表示為0.01,用科學計數法來表示為1 × 2的負2次方,0.25為正數,因此第31為為0,指數n向右得到,因此第30為為0,指數n(-2)減1得到-3,-3用32位來表示是0xFFFFFFFD,FD為1111 1101,從最低位開始數7個數出來放入23到29中

      

   (2)尾數部分,0.01的科學計數法為1.0 × 2的負2次方,小數點后面為0,因此,22 -0位中全部添0即可,最終表示為0011 1110 1000 0000 0000 0000 0000 0000,用十六進制表示為0x3E800000,我們反匯編觀察看看

例子4

  -0.25呢?如何表示呢?

  0xBE800000,讀者自己去試試吧,其實也不用試,十分簡單,我剛才不是算了0.25的了嗎?只需要把0.25的二進制表示復制過來,然后第31位改成1就是-0.25的值了~~~~

float與double

  float在內存中占4字節,double在內存中占8個字節,如果讀者您弄懂了上面的過程的話,相信你也一定明白了double的存儲過程,double存儲中提供了更多的尾數部分,這為小數點后面的精確度提供了更多的位數!使值更加精確,就像是99.99%與99.9999999999999999999999999999999999%的區別,就像是買黃金,如果只是商品交易,那么99.99%那么完全夠用了,因為99.99%它就可以說成是24K純金,但是科學實驗中呢?99.99%純嗎?它肯定沒有99.9999999999999999999999999999999999%純度高對吧!因為后者表示的精度更高,在平時中的程序中,float代表的精度其實已經足夠我們使用,因此,在平時代碼編寫中,float定義小數就足夠了,及其特殊情況下我們采用double,明白了小數在計算機中是如何存數的了嗎?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM