從IEEE754標準談C語言浮點數據類型

來源:互聯網
上載者:User

先看下面幾個問題,如果你能準確地回答,那麼此篇文章將不適合你:

  • 電腦中怎樣表示浮點數的,與整型的表示方法有什麼不同?
  • 32位精度的float類型和64位精度的double類型能表示浮點數最大範圍是多少?
  • 該C語言語句 printf("%d\n", 2.5);  輸出結果是什麼,為什嗎?

我先說在此之前我如果回答,答案如下:

  • 電腦中有符號整型採用補碼進行表示,浮點型怎麼表示沒想過。
  • float類型可以表示-232-1~232,double類型可以表示-264-1~264。
  • 輸出格式要求輸出整型,而數是浮點型,類型轉化之後輸出結果為2。

有一點可以明確,我的回答都是錯誤的。那好吧,下面是我查看一些資料總結出來的,希望能解釋清楚其中的”奧秘“。

 

IEEE754標準(以下簡稱”標準“)是使用最廣泛的浮點數運算標準,為許多CPU與浮點運算器所採用。該標準定義了表示浮點數的格式,如所示:

下面只討論二進位浮點數的表示,分成了三個部分:

符號位、指數、尾數,它們的含義可以類比科學計數法。如:

科學計數法中: 

(102.35045)10 = +1.0235045 × 102  符號位為正,指數是2,尾數是1.0235045。

(-0.00023103)10 = -2.3103 × 10-4  符號位為負,指數是-4,尾數是2.3103。

同樣在規格化二進位浮點數中:

(1001.0111010)2 = +1.001011101 × 23 符號位為正,指數是3,尾數是1.001011101。

(-0.0001010011)2 = -1.010011 × 2-4 符號位為負,指數是-4,尾數是1.010011。

由上面的執行個體可以知道,在二進位浮點數被規格化後,尾數的格式都是1.****,指數表示將小數點移動多少位可以實現規格化(向左指數加1,向右指數減1),因此可以正也可為負。

標準同時規定:

  • 符號位用1位表示,0表示正數,1表示負數;
  • 指數採用移碼錶示(原來的實際的指數值加上一個固定值得到的),這個固定值為2e-1-1(e為指數部分位元長度),之所以加上這個位移量,是為了將負數變成非負數,這樣兩個指數的大小很容易就可以比較。
  • 尾數採用原碼錶示,正如上所說,規格化二進位浮點數最高位均為1,那麼小數點前這個就沒必要用一個位元位去儲存,我們預設已經存在,稱為”隱藏位“。

標準規定了四種浮點數的表示方式:單精確度(32位)、雙精確度(64位)、延伸單精確度(43位元以上,很少使用)與延伸雙精確度(79位元以上,通常以80位元實做)。C語言中float和double浮點型分別對應的是單精確度和雙精確度浮點數,下面介紹這兩種浮點數的儲存格式:

如上面兩個例子,分別使用單精確度和雙精確度表示如下:

(1001.0111010)2 = +1.001011101 × 23單精確度: 符號位0,指數位為3+127=130(10000010),尾數1.001011101隱藏最高位1之後為001011101,因此表示為:0 10000010 00101110100000000000000雙精確度:只是在指數位上加的位移量不同,3+1023=1026(10000000010),表示為:0 10000000010 0010111010000000000000000000000000000000000000000000(-0.0001010011)2 = -1.010011 × 2-4單精確度:符號位1,指數位為-4+127=123(1111011),尾數1.010011 隱藏最高位1之後為010011,因此表示為:0 01111011 01001100000000000000000雙精確度:指數位為-4+1023=1019(1111111011),表示為:0 01111111011 0100110000000000000000000000000000000000000000000000

至此,應該已經解釋清楚了浮點數在電腦中的儲存格式和方法了,也就等於回答了上面的第一個問題,至於第二個問題,如果理解了上面所說的,求浮點數表示的範圍就應該很簡單了,下表為單精確度浮點數各種極值情況:

至於最後一個問題,我們寫一個C語言程式進行測試:

#include <stdio.h>int main(){        printf("%d\n", 2.5);        return 0;}

編譯運行結果如下:

[guohl@guohl]$ gcc -o test test.c -g[guohl@guohl]$ ./test 0

運行結果和我們預期的2不一樣,使用gdb調試,在main函數處插入斷點,並且反組譯碼main函數之後得到:

(gdb) break mainBreakpoint 1 at 0x8048415: file test.c, line 5.(gdb) runStarting program: /home/guohl/Documents/AS/test Breakpoint 1, main () at test.c:55        printf("%d\n", 2.5);(gdb) disassemble Dump of assembler code for function main:   0x0804840c <+0>:    push   %ebp   0x0804840d <+1>:    mov    %esp,%ebp   0x0804840f <+3>:    and    $0xfffffff0,%esp   0x08048412 <+6>:    sub    $0x10,%esp=> 0x08048415 <+9>:    fldl   0x80484e0   0x0804841b <+15>:    fstpl  0x4(%esp)   0x0804841f <+19>:    movl   $0x80484d8,(%esp)   0x08048426 <+26>:    call   0x80482f0 <printf@plt>   0x0804842b <+31>:    mov    $0x0,%eax   0x08048430 <+36>:    leave     0x08048431 <+37>:    ret    End of assembler dump.

fldl addr 指令將記憶體addr中的雙精確度浮點數載入到FPU寄存器堆棧,fstpl value 將雙精確度資料從FPU寄存器堆棧出棧,儲存到value中。因此,

0x08048415 <+9>: fldl 0x80484e0 0x0804841b <+15>: fstpl 0x4(%esp) 
首先取出記憶體0x80484e0處的雙精確度浮點數載入到FPU寄存器st0中,再從st0中取出放到esp-4處。先使用gdb -x命令查看記憶體0x80484e0處的內容:
(gdb) x/fg 0x80484e00x80484e0:    2.5(gdb) x/2xw 0x80484e00x80484e0:    0x00000000    0x40040000(gdb) x/8tb 0x80484e00x80484e0:    00000000    00000000    00000000    00000000    00000000    00000000    00000100    01000000
從上可以看到,以雙字的小數查看結果為2.5,由於我們平台採用的是小端格式儲存(little-edian,低位位元組儲存在低記憶體位置),所以將以位元組查看得到的結果恢複成下面的表示方法:
01000000 00000100 00000000 00000000 00000000 00000000 00000000 00000000

我們用IEEE754標準的雙精確度格式解析上面這段二進位,符號位為0,即為正;指數位為10000000000(1024)減去位移量1023為1;尾數0100…000,加上隱藏位1,為1.01(即十進位1.25)。所以結果為+1.25×21 = 2.5,符合我們的預期。

那麼fstpl指令將該浮點數載入到esp-4處作為printf函數的參數,再接著指令“movl $0x80484d8,(%esp) ”將輸出格式控制符"%d" 的指標儲存到esp指向的位置作為printf函數的函數,我們可以使用gdb查看記憶體0x80484d8處是不是格式控制符字串:

(gdb) x/4cb 0x80484d80x80484d8:    37 '%'    100 'd'    10 '\n'    0 '\000'
確實如我們所想,現在在調用printf之前函數堆棧的結構如下所示:

進入printf函數,解析第一個參數輸出格式控制字元串,遇到%d,函數從之前壓棧的參數取出一個整型即取到中esp+4處的值,以整型數輸出,為0。這就是我們上面運行./test 的輸出結果,而不是我想當然的程式會將2.5強制類型轉化為整型得到2!

 

 

參考資料:

http://zh.wikipedia.org/wiki/IEEE_754

Richard Blum, Professional Assembly Language

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.