【一天一個C++小知識】006. 浮點數在計算機內部的表示與轉換

  編寫程序時,兩個浮點數不能直接比較大小(無論單精度float雙精度double),這是因爲浮點數在計算機內部不能精確的表示。

  首先明確定點數和浮點數:定點數就是小數點位置固定不動的數,在計算機中,我們假設小數點在最前面,例如,001表示二進制小數0.001;10表示二進制小數0.10。這樣任何小於1且不小於0的小數都可以用定點數來表示。浮點數就是小數點位置不固定的數,這樣就需要一定的方法來表示浮點數。

  再看一下十進制小數和二進制小數的互相轉換:

  • 十進制小數轉二進制小數:小數部分乘以2,如果如果大於1,則記該位爲1,並將乘積減去1,如果小於1,則記該位爲0,乘積繼續乘以2。重複下去直到乘積正好爲1,最後一位取1,從前到後寫出記下的位的值。比如十進制的0.8125轉換爲二進制,首先0.8125x2=1.6250,記該位爲1,乘積減去1,;繼續乘2,0.625x2=1.250,記該位爲1,乘積減1,得0.250;繼續乘2,乘積爲0.500,小於1,記該位爲0;繼續乘2,乘積爲1.000,等於1,記該位爲1,最終結果0.1101
  • 二進制小數0.1101轉化爲十進制,1×21+1×22+0×231\times 2^{-1}+1\times 2^{-2}+0\times2^{-3}+1×24=0.8125+1\times2^{-4}=0.8125

  在計算機內部存儲時,浮點數主要分爲三部分來存儲:符號位、階碼部分(也有叫指數)和尾數部分【32位單精度float符號位佔1bit,階碼佔8bit,尾數部分佔23bit;64位雙精度double符號位佔1bit,階碼佔11bit,尾數部分佔52bit】。
在這裏插入圖片描述
  其中階碼存儲的是實際指數值的移碼,尾數部分存儲的是規約化後的尾數的補碼。移碼就是將實際的階碼值加上一個固定的數值而得到的數,在float型數據中規定該固定值/偏置量爲127,階碼有正負所以8爲二進制表示範圍-128—127,double中規定該固定值/偏置量爲1023,表示範圍-1024—1023。

  令nn表示階碼部分的長度,這個數就是e=2n11e=2^{n-1}-1。比如若真實的階碼爲2,float類型數據則加上127後爲129,階碼形式爲1000 0010。這樣做的目的就是即使最小的階碼(比如單精度爲-126)存儲的時候也會存儲爲1,方便了階碼的大小比較也省了一個符號位。這樣浮點數的計算公式可以表示爲(1)sign×2exponente×fraction2(-1)^sign\times2^{exponent-e}\times fraction_2.

  例如十進制的0.5表示爲二進制即爲(0.1)2=(1)0×21×(1.0)2(0.1)_2=(-1)^0\times 2^{-1}\times(1.0)_2,用單精度float存儲,則階碼部分用移碼錶示即-1+127=126,十進制0.5的存儲就是:

0 0111 1110 10000000000000000000000
  但是因爲規定尾數部分的整數部分恆爲1,所以表示的時候可以去掉,於是存儲爲:

0 0111 1110 00000000000000000000000

  這樣,存儲的二進制轉化爲實際數的計算公式爲:
(1)sign×2exponente×(fraction2+1)(-1)^{sign}\times2^{exponent-e}\times (fraction_2+1)

  當階碼部分爲0,尾數部分不爲0時,這個浮點數稱爲非規約形式浮點數,其階碼的偏移值比規約形式的浮點數大1。比如exponent=1,顯然這是規約形式浮點數,其實際指數應該是-126。而exponent=0,這是非規約形式浮點數,(若按照規約形式浮點數計算,其實際指數應爲-127(0-127))那麼根據前面提到的標準可知這個非規約形式浮點數的實際指數也是-126。所有的非規約浮點數比規約浮點數更接近0。


  一般地,float型125.5轉化爲標準浮點格式:125二進制111 1101,小數部分二進制爲1,則125.5二進制表示爲111 1101.1,由於規定尾數的 整數部分恆爲1,則表示爲1.1111011261.1111011*2^6,階碼爲6,加上127爲133,表示爲1000 0101,而對於尾數將整數部分1去掉爲111 1011,後面補0達到23位,則爲111 1011 0000 0000 0000 0000,也就是說十進制的125.5二進制表示形式爲0 10000101 11110110000000000000000,內存中存放方式爲從低地址到高地址依次爲00000000 00000000 11111011 01000010。根據這個二進制來求浮點數,因爲符號位爲0所以爲正數,階碼133-127=6,尾數111 1011 0000 0000 0000 0000,則其真實的尾數爲1.1111011,所以大小爲1.1111011×261.1111011\times 2^6,小數點右移6位得到111 1101.1,整數部分125,小數部分0.5。
  這樣我們也能得到float類型表示的最大範圍即1.111 1111 1111 1111 1111 1111*2^127=3.4E+38,所以範圍是-3.4E+38~3.4E+38。double類型類似1.111111111111111111111(小數點後52個1)*2^1023,範圍是-1.7E-308~1.7E+308
  運行驗證代碼:

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
    float a=125.5;
    char *p=(char *)&a;
    printf("%d\n",*p);
    printf("%d\n",*(p+1));
    printf("%d\n",*(p+2));
    printf("%d\n",*(p+3));
    return 0;
}

  輸出結果0,0,-5,66,因爲p和p+1指向的單元轉化爲十進制都是0,p+2指向的單元,因爲是char型指針,帶符號的數據類型,因此1111 1011,符號位爲1,則爲負數,又因爲計算機中二進制以補碼形式儲存,所以後面的部分應爲1000 0101也就是-5;最後一部分0100 0010,正數,大小66。

原碼:符號位加上真值的絕對值,即第一位表示符號,其餘位表示值,比如[+1]=[00000001][1]=[10000001][+1]=[0000 0001]_{原},[-1]=[1000 0001]_{原},所以8位二進制數的取值範圍是[1111 1111,0111 1111]即[-127,127]

反碼:正數的反碼是其本身,負數的反碼是在原碼的基礎上,符號位不變,其餘各個位取反,比如[1]=[11111110][-1]=[1111 1110]_{反}

補碼:正數的補碼是其本身,負數的補碼是在其原碼基礎上,符號位不變,其餘各位取反,最後加1,即反碼基礎加1,比如[-1]=[1111 1111]_{補}

無符號數無所謂原碼、反碼、補碼。


歡迎掃描二維碼關注微信公衆號 深度學習與數學   [每天獲取免費的大數據、AI等相關的學習資源、經典和最新的深度學習相關的論文研讀,算法和其他互聯網技能的學習,概率論、線性代數等高等數學知識的回顧]
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章