<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      c-primer-plus深入解讀系列-從二進(jìn)制到誤差:逐行拆解C語言浮點(diǎn)運(yùn)算中的4008175468544之謎

      前言

      書中的代碼示例 以及 我的測試環(huán)境信息如下所示,在該測試環(huán)境中,a 等于 4008175468544.000000。

      #include <stdio.h>
      
      int main(void)
      {
        float a,b;
      
        b = 2.0e20 + 1.0;
        a = b - 2.0e20;
        printf("%f \n",a);
      
        return 0;
      }
      
      Linux version 5.15.0-134-generic (buildd@lcy02-amd64-092) (gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #145~20.04.1-Ubuntu SMP Mon Feb 17 13:27:16 UTC 2025
      

      為什么會產(chǎn)生這樣的結(jié)果呢?

      由于書中的解釋不夠詳細(xì),我在閱讀至此處時,自然而然產(chǎn)生了想要深入研究的想法,遂將研究結(jié)果寫為本篇博客,供大家參考。針對這個問題,我的解決思路是,逐行分析每一句代碼都產(chǎn)生了什么效果,或許就可得知誤差出現(xiàn)在哪里。

      第一行代碼

      b = 2.0e20 + 1.0;
      

      常數(shù)的二進(jìn)制表示

      2.0e20會被當(dāng)作double類型的數(shù)據(jù)進(jìn)行處理,二進(jìn)制如下:

      0-10001000010-0101101011110001110101111000101101011000110001000000
      

      1.0也會被當(dāng)作double類型來接收,二進(jìn)制如下:

      0 01111111111 0000000000000000000000000000000000000000000000000000
      

      注:這里提供一套工具函數(shù),用來打印float和double類型的二進(jìn)制bit,用來驗(yàn)證很方便:

      #include <stdio.h>
      #include <stdint.h>
      
      void print_bits_float(float f)
      {
          union {
              float a;
              uint32_t b;
          }c;
          c.a = f;
      
          for (int i = 0; i < 32; i++) {
              printf("%d", c.b & 1 << (31 - i) ? 1 : 0);
      
              if (i == 0 || i == 8)
                  printf(" ");
          }
          printf("\n");
      }
      
      
      void print_bits_double(double d) 
      {
          union {
              double a;
              uint64_t b;
          }c;
          c.a = d;
      
          for (int i = 0; i < 64; i++) {
              //caution! 1ULL not 1 because 1 << or >> more than 31 is undefined behaviour.
              printf("%d", c.b & (1 ULL << (63 - i)) ? 1 : 0);
      
              if (i == 0 || i == 11)
                  printf(" ");
          }
          printf("\n");
      }
      

      (補(bǔ)充知識-可略過)2.0e20這個大數(shù)的二進(jìn)制是如何計算得出的?

      第一步:

      \(2 \times 10^{20}\)進(jìn)行質(zhì)因式分解:

      \(2 \times 10^{20} = 2 \times (2 \times 5)^{20} = 2^{21} \times 5^{20}\)

      這表明,\(2 \times 10^{20}\)是由\(2^{21}\)\(5^{20}\)的乘積構(gòu)成,也就是說,這個大數(shù)的二進(jìn)制形式可以看成52?的二進(jìn)制左移21位(即乘以221),也就是說,我們需要求得\(5^{20}\)的二進(jìn)制。


      第二步:

      \(5^{20}\)的二進(jìn)制:

      \(5^{20}\)的十進(jìn)制為:95367431640625

      十進(jìn)制求得二進(jìn)制的過程,簡單來說,即用95367431640625不斷除以2,得到余數(shù)1或者余數(shù)0的一個豎向序列,將其倒序即是所求二進(jìn)制。這里我直接寫出二進(jìn)制:

      10101101011110001110101111000101101011000110001(一共為47位)


      第三步:整體二進(jìn)制表示:

      由上述計算可得知,\(2 \times 10^{20}\)的二進(jìn)制為:

      \(10101101011110001110101111000101101011000110001 \times 2^{21}\) 我們將其規(guī)范化,得到

      \(1.0101101011110001110101111000101101011000110001 \times 2^{67}\)


      第四步:

      求出了整體的二進(jìn)制,double類型的具體存儲就簡單很多了。

      首先是指數(shù)部分:67 + 1023 = 1090,二進(jìn)制為10001000010

      然后是尾數(shù)部分:直接提取小數(shù)點(diǎn)之后的部分,只有46位,還需要填充6個0即可(滿足尾數(shù)52位的需求)

      加法運(yùn)算

      對階

      加法運(yùn)算的第一個步驟叫對階。什么叫對階呢?就是把較小的指數(shù)對齊至另外的較大的指數(shù),方便計算。

      比如:

      \(0.5_{10}\) + \(0.4375_{10}\) ,下標(biāo)代表進(jìn)制,所以這里是十進(jìn)制的0.5加十進(jìn)制的0.4375

      先把它們轉(zhuǎn)化為二進(jìn)制。\(0.5_{10} = 1.0_{2} \times 2^{-1}\)\(0.4375_{10} = 1.11 \times 2^{-2}\)

      找到較小指數(shù),即\(1.11 \times 2^{-2}\)(因?yàn)?2小于-1),將其指數(shù)-2對齊到較大的指數(shù)-1,對齊后變成這樣:

      \(1.11 \times 2^{-2} = 0.111 \times 2^{-1}\)

      這里要注意,因?yàn)橹笖?shù)變大了,所以小數(shù)點(diǎn)要左移相應(yīng)的位數(shù)以保證數(shù)據(jù)正確。這個例子中,這兩種表達(dá)方式對應(yīng)的數(shù)據(jù)都是 \(0.0111_{2}\)。如果換算不清楚的,可以像這樣,把不帶指數(shù)的數(shù)據(jù)寫出來對照一下,看你寫的數(shù)據(jù)運(yùn)算后是否和不帶指數(shù)的數(shù)據(jù)一樣。

      若是這里有讀者基礎(chǔ)不好,沒看懂,只要明白二進(jìn)制小數(shù) * 2的N次冪的含義,應(yīng)該就可以搞懂上述內(nèi)容了,即當(dāng)N大于0時,二進(jìn)制小數(shù) * 2的N次冪相當(dāng)于把二進(jìn)制小數(shù)的小數(shù)點(diǎn)右移N位;當(dāng)N小于0時,二進(jìn)制小數(shù) * 2的N次冪相當(dāng)于把小數(shù)點(diǎn)左移N位(聯(lián)想下十進(jìn)制就一定能搞懂了)

      回到我們的代碼,現(xiàn)在要加兩個數(shù)2.0e20 和 1.0,前面已經(jīng)討論過了它們的二進(jìn)制,接下來我們把它轉(zhuǎn)化為規(guī)范化數(shù):

      2.0e20的規(guī)范化數(shù)為(52位完整尾數(shù))

      \(1.0101101011110001110101111000101101011000110001000000 \times 2^{67}\)

      1.0的規(guī)范化數(shù)為(52位完整尾數(shù))(下述為了方便表達(dá),會用1.0替代)

      \(1.0000000000000000000000000000000000000000000000000000 \times 2^{0}\)

      需要找到較小的指數(shù),即\(1.0 \times 2^{0}\),對齊更大的指數(shù)\(2^{67}\)后,數(shù)據(jù)變?yōu)椋?/p>

      \(0.000...0001 \times 2^{67}\)((一共有67個0,小數(shù)點(diǎn)后66個0))

      這樣對階就完成了。

      有效數(shù)相加

      對階完畢后,第二個步驟叫有效數(shù)相加。官方叫尾數(shù)運(yùn)算,但其實(shí)這個運(yùn)算是要考慮前導(dǎo)1的,所以我認(rèn)為把它叫做有效數(shù)相加會更容易理解。(有效數(shù):包括了前導(dǎo)1的完整數(shù)據(jù),而非小數(shù)點(diǎn)后的尾數(shù)部分)

      2.0e20的有效數(shù)為:

      \(1.0101101011110001110101111000101101011000110001000000\)

      對階后的1.0的有效數(shù)為:

      0.000...0001(一共有67個0,小數(shù)點(diǎn)后66個0)

      其實(shí)仔細(xì)分析一下會發(fā)現(xiàn),對階1.0時會把小數(shù)點(diǎn)左移67位,那么當(dāng)小數(shù)點(diǎn)左移67位以后,1其實(shí)已經(jīng)消失了。這是因?yàn)椋p精度浮點(diǎn)數(shù)的尾數(shù)是52位,而對階后的1.0的有效數(shù)一共有68位,小數(shù)點(diǎn)后的52位被保留了,超過的部分會直接丟棄(而1正好處在丟棄區(qū)間內(nèi)),所以1.0在對階后尾數(shù)部分全部變成0,整個數(shù)據(jù)也變成了0.0。

      既然1.0已經(jīng)變成了0.0,那么相加之后的結(jié)果就還是2.0e20,若用上文提供的print_bits_double來分別打印 2.0e20 和 2.0e20 + 1.0,會發(fā)現(xiàn)打印出的二進(jìn)制值是完全一樣的。

      因?yàn)榻Y(jié)果本身已經(jīng)是規(guī)范化數(shù)了,所以不需要做最后一個步驟規(guī)范化了(本質(zhì)上就是移動小數(shù)點(diǎn) + 修改對應(yīng)的指數(shù),相信讀者有這個基礎(chǔ)可以理解)。

      等號右邊的加法部分已經(jīng)分析完畢,那么我們接著看賦值操作。

      雙精度賦值給單精度

      上文介紹了,2.0e20 + 1.0以后,結(jié)果其實(shí)就是2.0e20,但是此時它是雙精度的數(shù)據(jù),代碼中b的數(shù)據(jù)類型為float單精度,所以還需要做雙精度轉(zhuǎn)換為單精度的操作。

      首先是符號位和指數(shù)部分的轉(zhuǎn)換。

      因?yàn)殡p精度原數(shù)據(jù)的符號位是0,所以單精度的符號位也是0,代表正數(shù)。關(guān)于指數(shù)部分,我們在上文分析過了,規(guī)范化處理后,2.0e20的指數(shù)部分為67,轉(zhuǎn)換為單精度浮點(diǎn)數(shù)時,需要再加127,即127 + 67 = 194,二進(jìn)制為11000010。

      其次是尾數(shù)部分的轉(zhuǎn)換。

      因?yàn)殡p精度的尾數(shù)部分有52位,而單精度的尾數(shù)只有23位,既然單精度尾數(shù)部分無法完整容納原雙精度的尾數(shù)部分,那么就需要考慮如何舍入。IEEE 754標(biāo)準(zhǔn)提供了四種舍入模式,分別是:

      1. Round to Nearest (Ties to Even):默認(rèn)舍入模式,將結(jié)果舍入到最接近的可表示值。

      2. Round toward Zero (Truncation):直接截斷超出目標(biāo)精度的部分。

      3. Round toward +∞ (Ceiling):結(jié)果始終向正無窮方向調(diào)整。對正數(shù)而言,剩余部分全為0則直接截尾,不全為0則向最低有效位進(jìn)1;負(fù)數(shù)的話不看剩余部分,直接截尾。

      4. Round toward ?∞ (Floor):結(jié)果始終向負(fù)無窮方向調(diào)整。對負(fù)數(shù)而言,剩余部分全為0則直接截尾,不全為0則向最低有效位進(jìn)1;正數(shù)的話不看剩余部分,直接截尾。

      下面我們著重講解Round to Nearest (Ties to Even)模式,因?yàn)樵谖覀兊膶?shí)驗(yàn)中,是使用第一種模式進(jìn)行舍入的。

      Round to Nearest (Ties to Even)模式

      該模式的規(guī)則為:首先向最近的有效數(shù)舍入,如果它與兩個相鄰的有效數(shù)距離一樣時(即它是中間數(shù),halfway),那么舍入到最近的偶數(shù)有效數(shù)。

      為了方便理解,我們以二進(jìn)制數(shù)據(jù)來舉例子,假如需要保留的有效位為4位:

      1.001(該四位為有效位)+ 后6位(待判斷)

      最近的有效數(shù)有兩個,分別為:1.001 和 1.010 。

      接著分析下后六位,其取值范圍是 000000 到 111111,我們把它分成兩個區(qū)間來分析:

      分析 000000-011111 取值范圍

      當(dāng)后六位取值范圍在000000-011111時,針對每一個數(shù)據(jù),其與1.001 000000 的距離 都小于 其與1.010 000000 的距離。

      我們以這個區(qū)間范圍內(nèi)最大的數(shù)據(jù) 1.001 011111為例:

      • 該數(shù)據(jù)與1.001的距離為 1.001 011111 - 1.001 000000 = 0.000 011111

      • 該數(shù)據(jù)與1.010的距離為 1.010 000000 - 1.001 011111 = 0.000 100001

      因此,該數(shù)據(jù)的最近有效數(shù)為 1.001,而該數(shù)據(jù)又是全區(qū)間內(nèi)的最大值,所以該區(qū)間范圍內(nèi)的其他數(shù)據(jù)的最近有效數(shù)均為1.001。

      分析 100001 - 111111 取值范圍

      同理,當(dāng)后六位取值范圍在 100001 - 111111時 ,針對每一個是數(shù)據(jù),其與 1.001 000000的距離 都大于 其與1.010 000000的距離。

      我們以這個區(qū)間范圍內(nèi)的最小數(shù)據(jù) 1.001 100001 為例:

      • 該數(shù)據(jù)與1.001的距離為 1.001 100001 - 1.001 000000 = 0.000 100001

      • 該數(shù)據(jù)與1.010的距離為 1.010 000000 - 1.001 100001 = 0.000 011111

      因此,該數(shù)據(jù)的最近有效數(shù)為 1.010,而該數(shù)據(jù)又是全區(qū)間內(nèi)的最小值,所以該區(qū)間范圍內(nèi)的其他數(shù)據(jù)的最近有效數(shù)均為1.010。

      分析中間值 1.001 100000

      下面看下唯一還沒有被分析過的數(shù):1.001 100000,計算得知:

      • 其與1.001的距離為 0.000 100000

      • 其與1.010 的距離也為 0.000 100000

      因?yàn)榫嚯x一樣,所以該數(shù)據(jù)的舍入規(guī)則為舍入到最近的有效偶數(shù),即為 1.010。

      由此可見,當(dāng)剩余部分的從左至右第一位為0時,直接截去;當(dāng)剩余部分等于1000..000時,舍入到最近的偶數(shù);否則,需要進(jìn)位。

      實(shí)際賦值分析

      了解了舍入規(guī)則后,就可以回到我們的主題代碼案例,繼續(xù)我們的賦值之旅了。

      我們已經(jīng)了解了2.0e20 + 1.0之后1.0會"消失",結(jié)果依舊是2.0e20,它的雙精度二進(jìn)制我們再復(fù)習(xí)一遍:

      0-10001000010-0101101011110001110101111000101101011000110001000000

      當(dāng)需要轉(zhuǎn)化為單精度浮點(diǎn)數(shù)時:

      • 符號位:直接把雙精度浮點(diǎn)數(shù)的符號位填入即可,為0

      • 指數(shù)部分:前文分析過,十進(jìn)制為194,二進(jìn)制為11000010

      • 尾數(shù)部分:需要保留前23位,后29位為判斷位

        • 前23位:01011010111100011101011

        • 后29位:11000101101011000110001000000

          根據(jù)學(xué)習(xí)過的舍入規(guī)則,有效部分以外的剩余部分(后29位)的第一位非0,且整體非10000...000的形式,所以需要直接進(jìn)位,那么舍入以后的尾數(shù)部分即:

          01011010111100011101011 + 1 = 01011010111100011101100

      • 轉(zhuǎn)化以后的單精度浮點(diǎn)數(shù)二進(jìn)制即:0-11000010-01011010111100011101100。

      至此,第一行代碼就分析完畢了。

      逐行分析解讀-第二行代碼

      上文我們已經(jīng)分析完第一行代碼,接下來,我們看下至關(guān)重要的第二行代碼:

       a = b - 2.0e20;
      

      其實(shí)有了上文的基礎(chǔ),我們馬上就可以得知,2.0e20是用double類型來接收的,其底層的二進(jìn)制我們也已經(jīng)清楚明了。但是在實(shí)際做減法之前,還需要將b從單精度浮點(diǎn)數(shù)提升為雙精度浮點(diǎn)數(shù)。雙精度轉(zhuǎn)換為單精度的過程我們已經(jīng)分析過了,因?yàn)樯崛胍?guī)則會導(dǎo)致進(jìn)位,而從單精度提升到雙精度則要簡單很多,符號位與指數(shù)位我們不再分析,尾數(shù)部分只需要把單精度浮點(diǎn)數(shù)的23位尾數(shù)直接填充至雙精度浮點(diǎn)數(shù)尾數(shù)部分的高位,然后剩余部分補(bǔ)0即可,轉(zhuǎn)換以后的二進(jìn)制如下:

      0-10001000010-01011010111100011101100 00000000000000000000000000000

      二者類型都為double后,就可以做減法了。

      做減法時,先把前導(dǎo)1補(bǔ)充上,然后按位依次相減即可:

      1.0101101011110001110110000000000000000000000000000000

      -(減法)

      1.0101101011110001110101111000101101011000110001000000

      --------------------------------------------------------------------------

      0.0000000000000000000000000111010010100111001111000000

      不要忘記了后面的指數(shù),我們把完整的浮點(diǎn)數(shù)寫出來:

      \(0.0000000000000000000000000111010010100111001111000000 \times 2^{67}\)

      這個形式還不是規(guī)范化形式,需要轉(zhuǎn)換為規(guī)范化表達(dá):

      \(1.11010010100111001111000000 \times 2^{41}\)

      最后,我們需要把這個數(shù)據(jù)轉(zhuǎn)化為單精度浮點(diǎn)數(shù),才能存儲到a變量中。因?yàn)槲矓?shù)部分的有效位一共只有20位,所以用單精度浮點(diǎn)數(shù)的23位完全可以表示,就不需要舍入了,轉(zhuǎn)化后的單精度浮點(diǎn)數(shù)二進(jìn)制為:

      • 符號位:0

      • 指數(shù)部分:41 + 127 = 168,轉(zhuǎn)化為二進(jìn)制是10101000

      • 尾數(shù)部分:11010010100111001111000

      這個數(shù),轉(zhuǎn)化為十進(jìn)制浮點(diǎn)數(shù)打印出來,便是:4008175468544.000000,至此我們的分析之旅結(jié)束了。

      總結(jié)

      下面,我們總結(jié)下整個過程中的關(guān)鍵步驟:

      • 2.0e20 和 1.0都被當(dāng)作double類型來接收

      • 2.0e20 + 1.0以后,1.0因?yàn)閷﹄A,有效部分在小數(shù)點(diǎn)左移過程中消失(存在于大于52位的位置)

      • 雙精度轉(zhuǎn)換為單精度的過程,因?yàn)樯崛胍?guī)則,產(chǎn)生了進(jìn)位,導(dǎo)致實(shí)際存儲的float類型的值要比原來的double類型的值要大一些

      • 后續(xù)把單精度提升為雙精度,導(dǎo)致后面29位直接補(bǔ)0

      所以,在整個過程中,誤差出現(xiàn)的關(guān)鍵便在于:

      1. 第一次雙精度加法后賦值給單精度,導(dǎo)致雙精度數(shù)據(jù)變?yōu)閱尉葦?shù)據(jù),而舍入規(guī)則導(dǎo)致了進(jìn)位;

      2. 后續(xù)減法計算過程中,又把單精度數(shù)據(jù)轉(zhuǎn)化位雙精度,后29位直接補(bǔ)0,導(dǎo)致比實(shí)際雙精度的2.0e20的二進(jìn)制要大一些,產(chǎn)生的誤差便是最終的結(jié)果:4008175468544.000000。

      這也提示我們,在計算過程中,應(yīng)該使用統(tǒng)一的數(shù)據(jù)類型,避免出現(xiàn)雙精度轉(zhuǎn)換單精度這種隱含操作。

      上述代碼案例若使用下面兩種寫法,則都會正確輸出0.0

      #include <stdio.h>
      
      int main(void)
      {
        double a,b;
      
        b = 2.0e20 + 1.0;
        a = b - 2.0e20;
        printf("%f \n",a);
      
        return 0;
      }
      
      #include <stdio.h>
      
      int main(void)
      {
        float a,b;
      
        b = 2.0e20f + 1.0f;
        a = b - 2.0e20f;
        printf("%f \n",a);
      
        return 0;
      }
      
      posted @ 2025-03-27 16:10  Ging  閱讀(736)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 国产精品无码成人午夜电影| 日韩精品一区二区三区日韩| 国产对白熟女受不了了| 性xxxx视频播放免费| 内射中出无码护士在线| 亚洲天堂精品一区二区| 九九热在线免费播放视频| 双乳奶水饱满少妇呻吟免费看 | 亚洲综合另类小说色区一| 日韩a∨精品日韩在线观看| 日韩 一区二区在线观看| 99热久久这里只有精品| 国产午夜精品理论大片| 国内自拍偷拍一区二区三区| 91超碰在线精品| 激情六月丁香婷婷四房播| 九九热在线观看精品视频| 国产成人精品免费视频大全| 国产成人片无码视频| 国产精品免费观看色悠悠| 国产一区二区三区AV在线无码观看| 内射视频福利在线观看| 国产偷国产偷亚洲高清午夜| 亚洲综合在线日韩av| 69天堂人成无码免费视频| 熟妇的味道hd中文字幕| 国产精品一线二线三线区| 91人妻无码成人精品一区91| 日韩中文日韩中文字幕亚| 亚洲激情一区二区三区视频| 人人入人人爱| 高清一区二区三区不卡视频| 中文字幕日韩精品亚洲一区| 亚洲第一无码专区天堂| 久久精品免费自拍视频| 国内自拍小视频在线看 | 精品国产成人三级在线观看| 国产第一页浮力影院入口| 狠狠色婷婷久久综合频道日韩 | 国产亚洲精品精品精品| 激情综合色综合啪啪开心|