電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 28|回復(fù): 0
收起左側(cè)

浮點數(shù)精度問題全解析

[復(fù)制鏈接]

502

主題

502

帖子

3383

積分

四級會員

Rank: 4

積分
3383
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 4 天前 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
0、導(dǎo)讀這篇文章主要探討了浮點數(shù)在計算機中的表示、存儲和精度問題。通過詳細(xì)的解釋和示例,您將了解浮點數(shù)誤差的根源。文章內(nèi)容較多,大約3700余字,閱讀時間約為10分鐘,建議先收藏,待有空時再細(xì)細(xì)品讀。
1、引言0.1 + 0.2 為什么不等于 0.3 ?
當(dāng)被問及浮點數(shù)為何存在誤差時,你將如何回答?
沒看完這篇文章之前你可能會回答:"哼,反正我就知道有誤差..."
閱讀完這篇文章后,你將能夠更準(zhǔn)確地回答這類問題,讓我們開始這段學(xué)習(xí)之旅吧!
2、浮點數(shù)存儲格式浮點型在內(nèi)存中的存儲不是像整形那樣直接存儲的,而是用一種二進制的科學(xué)計數(shù)法來表示的,具體的數(shù)學(xué)表達(dá)式為
V = (-1) s × M × 2 e其中,e = E - 127
在計算機科學(xué)領(lǐng)域,IEEE 754 是一種標(biāo)準(zhǔn),用于定義浮點數(shù)的表示方法,浮點型數(shù)據(jù)的存儲格式如下

請務(wù)必記住,尾數(shù)存儲用原碼,階碼存儲用移碼
  • S(符號位):0代表正數(shù),1代表負(fù)數(shù)。
  • E(階碼):指數(shù)字段需要同時表示正指數(shù)和負(fù)指數(shù)。為了得到存儲的指數(shù),在實際指數(shù)上加一個偏置,其中e=E-127。
  • M(尾數(shù)):一個規(guī)范化尾數(shù)就是小數(shù)點左邊只有一個1,然后是小數(shù)點后面的尾數(shù)部分。注意本文后續(xù)使用的e表示科學(xué)計數(shù)法中的指數(shù)部分,E表示存儲格式中的階碼,默認(rèn)的對象都指單精度的浮點數(shù)。
    3、轉(zhuǎn)換流程接下來我選擇了一個戀愛腦的數(shù)字,將1314.520轉(zhuǎn)換到32位單精度IEEE 754二進制浮點表示標(biāo)準(zhǔn)。
    3.1、將整數(shù)部分轉(zhuǎn)換為二進制將整數(shù)部分反復(fù)除以2,并記錄每次的余數(shù),直到商為0為止。
    division = quotient + remainder;
    1314 ÷ 2 = 657 + 0;
    657  ÷ 2 = 328 + 1;
    328  ÷ 2 = 164 + 0;
    164  ÷ 2 = 82  + 0;
    82   ÷ 2 = 41  + 0;
    41   ÷ 2 = 20  + 1;
    20   ÷ 2 = 10  + 0;
    10   ÷ 2 = 5   + 0;
    5    ÷ 2 = 2   + 1;
    2    ÷ 2 = 1   + 0;
    1    ÷ 2 = 0   + 1;
    從上面構(gòu)造的列表的底部開始取所有余數(shù),即為整數(shù)部分的二進制表示。131410=101 0010 00102
    3.2、將小數(shù)部分轉(zhuǎn)為二進制將小數(shù)部分不斷乘以2,并記錄每次的整數(shù)部分,直到小數(shù)部分為0或達(dá)到所需的精度為止
    #) multiplying = integer + fractional part;
    1) 0.52 × 2 = 1 + 0.04;
    2) 0.04 × 2 = 0 + 0.08;
    3) 0.08 × 2 = 0 + 0.16;
    4) 0.16 × 2 = 0 + 0.32;
    5) 0.32 × 2 = 0 + 0.64;
    6) 0.64 × 2 = 1 + 0.28;
    7) 0.28 × 2 = 0 + 0.56;
    8) 0.56 × 2 = 1 + 0.12;
    9) 0.12 × 2 = 0 + 0.24;
    10) 0.24 × 2 = 0 + 0.48;
    11) 0.48 × 2 = 0 + 0.96;
    12) 0.96 × 2 = 1 + 0.92;
    13) 0.92 × 2 = 1 + 0.84;
    14) 0.84 × 2 = 1 + 0.68;
    15) 0.68 × 2 = 1 + 0.36;
    16) 0.36 × 2 = 0 + 0.72;
    17) 0.72 × 2 = 1 + 0.44;
    18) 0.44 × 2 = 0 + 0.88;
    19) 0.88 × 2 = 1 + 0.76;
    20) 0.76 × 2 = 1 + 0.52;
    21) 0.52 × 2 = 1 + 0.04;
    22) 0.04 × 2 = 0 + 0.08;
    23) 0.08 × 2 = 0 + 0.16;
    24) 0.16 × 2 = 0 + 0.32;
    雖然我們沒有得到任何等于0的小數(shù)部分,但是我們有足夠的迭代(超過尾數(shù)限制)。
    從頂部開始依次取乘法運算的所有整數(shù)部分,即為小數(shù)部分的二進制:0.5210=0.1000 0101 0001 1110 1011 10002
    3.3、規(guī)范化前面得出了整數(shù)以及小數(shù)部分的二進制表示,合并以后即:
    1314.5210= 101 0010 0010.1000 0101 0001 1110 1011 10002
    將小數(shù)點向左移動 10 位,使其左邊只剩下一位非零的數(shù)字
    1314.5210= 101 0010 0010.1000 0101 0001 1110 1011 10002= 101 0010 0010.1000 0101 0001 1110 1011 10002 ×2 0= 1.0100 1000 1010 0001 0100 0111 1010 1110 002 ×2 10
    再回顧一下浮點數(shù)的數(shù)學(xué)表達(dá)式 V = (-1) s × M × 2 e 由此可知
    s = 0
    M = 1.0100 1000 1010 0001 0100 0111 1010 1110 00
    e = 10
    3.4、調(diào)整階碼根據(jù)規(guī)范化得知指數(shù) e = 10,又根據(jù)公式 e = E - 127 可得知道 E=137,所以八位階碼的二進制表示如下所示:
    E = 13710 = 1000 10012
    3.5、尾數(shù)舍入由第三步規(guī)范化得出的尾數(shù)M有34位,但是存儲格式中尾數(shù)只有23位,下面劃線的是多出的部分,所以需要對尾數(shù)按照一定的方式進行四舍五入。
    M = 1. 0100 1000 1010 0001 0100 011 1 1010 1110 00
    一共有四種舍入方式,
  • 向偶數(shù)舍入,就近舍入(默認(rèn))。
  • 朝0舍入:即朝數(shù)軸零點方向舍入,即直接截尾。
  • 朝正無窮舍入:對正數(shù)而言,只要多余位不全為0則向最低有效位進1;負(fù)數(shù)則直接截尾。
  • 朝負(fù)無窮舍入:對負(fù)數(shù)而言,向最低有效位進1;正數(shù)若多余位不全部為0則簡單截尾。向偶數(shù)舍入,簡單理解就要讓尾數(shù)的最后一位為0,讓其保持偶數(shù),能夠被2整除。當(dāng)尾數(shù)的最低位為0時,已經(jīng)是屬于偶數(shù)了,無需處理。當(dāng)尾數(shù)最低位為1時,需要加1,使其保持偶數(shù)。
    因為本例計算出尾數(shù)的最后一位為1,按照就近舍入(向偶舍入)原則需要加1使其保持偶數(shù)。
    所以經(jīng)過調(diào)整后的M為
    M = 0100 1000 1010 0001 0100 011 + 1
    M = 0100 1000 1010 0001 0100 100
    3.6、組三元素根據(jù)前面的步驟可以得知
    s = 0
    E = 1000 1001 2
    M = 0100 1000 1010 0001 0100 100 2
    1324.5210 = 0-1000 1001-0100 1000 1010 0001 0100 1002
    我們?nèi)ヒ粋轉(zhuǎn)換網(wǎng)站上驗證一下轉(zhuǎn)換結(jié)果,網(wǎng)站鏈接放在文章末尾了。

    floatConverterIEEE754可以看到,跟我們轉(zhuǎn)換的結(jié)果是相同的,說明網(wǎng)站轉(zhuǎn)換也是選擇向偶數(shù)舍入的。
    4、單/雙精度浮點數(shù)比較4.1、存儲格式類型符號位指數(shù)長度(Bit)尾數(shù)長度(Bit)float1823double111524.2、精度浮點數(shù)的精度是由尾數(shù)的位數(shù)來決定的。
    對于float型浮點數(shù),尾數(shù)部分23位,換算成十進制就是 2^23=8388608,所以十進制精度只有6 ~ 7位;
    這里的數(shù)字6和7可能會引起疑問,如何理解它們呢?
    由于浮點數(shù)尾數(shù)的舍入問題,最后一位可能存在舍入誤差,因此不完全準(zhǔn)確。因此,可以準(zhǔn)確表示的是后六位,而第七位則可能含有誤差。
    對于double型浮點數(shù),尾數(shù)部分52位,換算成十進制就是 2^52 = 4503599627370496,所以十進制精度只有15 ~ 16位
    類型有效位字節(jié)數(shù)float6 - 74double15 - 1684.3、浮點數(shù)范圍類型最小值最大值float1.175494351 E - 383.402823466 E + 38double2.2250738585072014 E - 3081.7976931348623158 E + 3084.4、浮點數(shù)比較浮點數(shù)的比較通常用兩數(shù)之差的絕對值小于一個自定義的數(shù)值時,代表兩者相等,如下所示:
    /**
    *Author:(公眾號:typedef)
    */
    #define FLOAT_EPSILON (0.000001) //Define your own tolerance
    #define FloatIsEqual(a, b) ((fabs((a)-(b)))
    另外一種方法是將浮點數(shù)同時放大一個倍數(shù),然后轉(zhuǎn)成整數(shù)之間的比較,比如同時放大10000倍等。
    5、階碼相關(guān)問題探索首先階碼E是用移碼表示的,那么問題來了,什么叫移碼?移碼怎么計算?移碼的含義是?浮點數(shù)為什么要用移碼表示?
    在解答這些知識點時,我們需要下面兩點需要達(dá)成一致
  • 階碼使用的是非標(biāo)準(zhǔn)移碼
  • 階碼是一個無符號的整數(shù)5.1、什么是移碼移碼是補碼表示中最高符號位取反的結(jié)果。舉個例子,上面計算1314.52時,指數(shù)是為10的。
    +1010 = 0000 10102(真值)
    原碼:0000 1010
    反碼:0000 1010
    補碼:0000 1010
    移碼:1000 1010
    所以10對應(yīng)標(biāo)準(zhǔn)的移碼 1000 1010 。
    5.2、如何計算移碼注意浮點數(shù)中移碼的計算是非標(biāo)準(zhǔn)的,僅偏移2n-1-1=127。所以移碼的計算公式如下所示,其中n為階碼的位數(shù):
    E = e + 2 n-1 - 1
    E = e + 127
    所以10對應(yīng)的移碼為137。
    5.3、為什么要用移碼表示它通過將數(shù)值加上一個固定的偏移量,使得原本可能是負(fù)數(shù)的數(shù)值變?yōu)榉秦?fù)數(shù),從而簡化了計算機中有符號數(shù)的表示和比較操作。使得計算機能夠直接使用整數(shù)運算來比較浮點數(shù)的大小。
    6、指數(shù)e6.1、指數(shù)范圍浮點數(shù)指數(shù)部分的實際取值范圍是 [-2(e-1)+2, 2(e-1)-1],其中 e 為指數(shù)所占位數(shù)。32位浮點數(shù),指數(shù)占8位,實際取值范圍是 [-126, 127]。
    -127用作表示0,128 用作表示無窮大和 NaN。NaN 是 "Not a Number" 的縮寫,中文意思是“非數(shù)字”,通常用于表示一個未定義或不可表示的值。
    換言之,8位階碼的表示范圍是[0, 255],其中0和255用于表示特殊值。因此,根據(jù)公式推導(dǎo),指數(shù)e的實際取值范圍是[-126, 127]。
    6.2、特殊值形式指數(shù)(e)階碼(E)小數(shù)部分零-12700無窮1282e-1 = 2550NaN(非數(shù))1282e-1 = 255非07、文中問題解答此時再來回答文中引言提出的問題, 0.1 + 0.2 為什么不等于 0.3 ?
    /**
    * Author:(公眾號:typedef)
    */
    #include
    int main() {
      double a = 0.1 + 0.2;
      printf("%.17f", a);
    }
    輸出為0.30000000000000004,由于在尾數(shù)舍入時會帶來一定的誤差,所以并不完全相等。
    當(dāng)在被問及浮點數(shù)為何存在誤差時,你將如何回答?歡迎文章留言說出你的看法。
    如果不從技術(shù)的角度回答這個問題,可以這樣回答:整數(shù)是離散的,有限的并能夠被計算機表示的,小數(shù)部分是連續(xù)的,包含無窮多的數(shù),數(shù)量之多是無法被計算機存儲的,只能存儲計算機能夠表示的最接近這個數(shù)值的小數(shù)部分,所以可能會不相等。
    8、參考鏈接
  • https://www.cnblogs.com/gyunf/p/12816817.html
  • https://www.h-schmidt.net/FloatConverter/IEEE754.html
  • https://zh.wikipedia.org/wiki/IEEE_754
  • https://docs.pingcode.com/ask/304021.html9、總結(jié)本篇文章深入分析了浮點數(shù)的存儲格式到轉(zhuǎn)換流程,再到指數(shù)e以及階碼E的探索,大家應(yīng)該對浮點數(shù)有了更全面的理解。
    猜你喜歡:
    一個非常輕量的嵌入式日志庫!
    一個非常輕量的嵌入式線程池庫!
    Github上熱門 C 語言項目匯總!
    實用 | 10分鐘教你通過網(wǎng)頁點燈
    WiFi6+藍(lán)牙+星閃,三合一開發(fā)板,真香!
  • 回復(fù)

    使用道具 舉報

    發(fā)表回復(fù)

    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規(guī)則


    聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表