小數在內存中是如何存儲的?

小數在內存中是如何存儲的?

文本關鍵字:小數、float、double、浮點數、精度

一、IEEE 754(二進制浮點數算術標準)

在學習進制轉換時,我們瞭解到:我們經常使用的十進制數是轉換爲二進制進行存儲的,只需要按照順序將轉換後的結果放在對應的位置上就行了。其實小數的存儲也是基於二進制的,不過由於小數由整數部分和小數部分組成,爲了方便表示和比較,會使用另外的方式來存儲。
IEEE 754是最廣泛使用的浮點數運算標準,在標準中規定了四種表示浮點數值的方式:

  • 單精度:32位 - 4字節
  • 雙精度:64位 - 8字節
  • 延伸單精度:43+
  • 延伸雙精度:79+
    對於進制轉換不清楚的同學可以進傳送門:進制之間如何轉換?

    1. 存儲結構

    小數在內存中的存儲由三部分組成,分別是符號、階碼(或稱指數)、尾數。符號位我們很熟悉,只佔一位,並且出現在最高位,0爲正,1爲負。

  • 單精度:符號1位,階碼8位,尾數23位
  • 雙精度:符號1位,階碼11位,尾數52位
  • 延伸精度很少使用,不做介紹

小數在內存中是如何存儲的?

2. 存儲方式

一個十進制的小數在進行存儲時,首先要將整數部分與小數部分都轉換爲二進制,然後再整理成類似科學技術法的形式,即:移動小數點,使得小數點的左邊只有一位,並且只可能爲1(因爲是二進制),小數點右側的部分即爲尾數部分,移動小數點的位數將會被記錄在指數部分中。爲了能夠透徹的理解十進制小數轉化存儲在內容中的過程,我們還需要了解一個概念:階碼。

二、階碼(指數)

1. 定義

對於一個二進制數,我們總可以把它整理成:尾數 ✖️ 2的P次方的形式,其中P就被定義爲階碼,我們也可以認爲2是底數,P爲指數,以整數形式表示。

2. 爲什麼小數被稱作浮點數?

  • 定點小數

在早期計算機中,爲了節省硬件資源,階碼P的值是被固定的,那麼小數的表示形式也同時被固定了。規定第一位爲符號位,小數點固定在第一位後面,這種小數是純小數,被稱爲定點小數。

  • 浮點小數

與定點小數相對的,如果階碼P可變,那這種小數表示法就被稱爲浮點表示,這樣的數也就被稱爲浮點數了。更爲重要的一點,P指明瞭小數點的位置。

3. 移碼

明白了階碼的概念,也瞭解了浮點數的前世今生,那麼我們大費周章的說這個概念幹什麼呢?沒錯,重點來了,就是爲了這個移碼的碼制。在進行小數點移動時,需要先將十進制數轉換爲二進制,再去移動小數點,保證小數點左側只有一位,且數值爲1。

  • 對於絕對值大於2的數,這個時候我們向左移動小數點,對應的指數爲正數;
  • 對於一個絕對值小於1的數,這個時候我們向右移動小數點,對應的指數爲負數;
  • 絕對值在1和2之間的數嘞?這個時候不用移動好叭。。。

那麼問題就來了,我們的指數有的時候正,有的時候負。But!更爲嚴重的問題是,在指數部分對應的區間並沒有符號位這個東西,最前面的符號位代表的是小數本身的正負,這就使得存儲和比較都變得困難,所以我們希望通過一種修正的方式避開正負號的問題。怎麼做呢?以float爲例,指數部分長度爲8。
原有帶符號位的8個bit的存儲範圍是-128 ~ 127(不明白的同學可以進傳送門爲什麼一個byte的存儲範圍是-128~127?),也就是說可以記錄-128次方到+127方之間的所有指數值。如果忽略符號位,把它也當做一個數據的存儲位,那麼範圍就是0~255,我們取這個數的一半作爲修正值,即:127,把每次移動小數點後獲得的指數值都加上127。

  • 小數點向左移動3位,對應的指數爲+3,存入指數部分的值即爲130的二進制表示
  • 小數點向右移動2位,對應的指數爲-2,存入指數部分的值即爲125的二進制表示

這樣的好處就是避開了符號的問題,同時,原有的指數的值也得到的了保存,取出的時候減掉127就好了。那麼直觀的講,原來的範圍是-128 ~ 127,加上127之後範圍應該變成-1 ~ 254,貌似對應關係有問題呀~這其實是一個很簡單的二進制換算問題,對於有符號數,最高位爲符號位,用1代表負數,-128的補碼爲:1000 0000,但是這在無符號數眼裏的值爲128,-1的補碼爲:1111 1111,但是在無符號數眼裏值爲255。所以我們不能直接通過加減法得出這個取值範圍,而應該結合二進制存儲的規則(不明白的同學可以二進傳送門查看補碼相關的知識:爲什麼一個byte的存儲範圍是-128~127?)。

三、小數的進制轉換

說了這麼久,我們用幾個例子來給大家演示一下,會給大家列出小數在內存中存儲的完整表示,在這之前還是需要先學習一下十進制小數應該怎麼轉換爲二進制(讀者內心:我太難了。。。)。

1. 十進制轉二進制

小數分爲整數部分和小數部分,整數部分的轉換照常進行,不斷的除2得到,小數部分剛好是不斷的乘2得到,一直到小數部分爲0,或者已經達到了對應的精度,以69.3125爲例。

  • 整數部分:69 = 64 + 4 + 1 = 2^6 + 2^2 + 2^0
    • 對應的二進制數爲:0100 0101
  • 小數部分:轉換過程如下 -> 不斷乘2,取出結果中的整數部分
    • 對應的二進制數爲:0101

小數在內存中是如何存儲的?

  • 最終轉換結果:0100 0101.0101

    2. 二進制轉十進制

    由二進制轉換爲十進制比較簡單,就是運算規則做相反的運算,整數部分是做除法得到的,那麼轉換回去的時候就是做乘法,小數部分是做乘法得到的,那麼轉換回去的時候就做除法,以0100 0101.0101爲例。

  • 整數部分:2^6 + 2^2 + 2^0 = 64 + 4 + 1 = 69
  • 小數部分:0 x 2^-1 + 1 x 2^-2 + 0 x 2^-3 + 1 x 2^-4 = 0.3125

可以看到規律其實是統一的,就是從左至右根據二進制數乘以2的n次方,從左至右n的值不斷遞減,在個位處,n的值爲0,進入小數部分n的值爲負數,在運算上的體現爲除法。

3. 小數在內存中的存儲表示

  • 99.9

9.9的二進制表示:1100011.111001100110011001100110011001100110011001101。現在我們需要將小數點左移6位,對應的指數值爲+6。此時小數點右側的位數爲51位,這些將會被存放在尾數部分,如果使用double類型可以將數據全部記錄,但是如果使用float類型,由於尾數部分只有23位,所有隻能記錄部分的數據,誤差也就產生了!
整理一下,符號位爲0,指數部分爲6+127=133,尾數部分直接丟進去,能裝多少裝多少,以float爲例。
最終表示爲:0 10000101 10001111100110011001100

  • 0.226

0.226的二進制表示:0.0011100111011011001000101101000011100101011000000100001。此時小數點需要右移3位,對應的指數值爲-3,剩下的尾數部分同樣能塞多少塞多少。
整理一下,符號位爲0,指數部分爲-3+127=124,以float爲例。
最終表示爲:0 1111100 11001110110110010001011

四、float與double

1. 精度範圍

從上面的例子我們可以看到,當一個小數在存儲的過程中,誤差就已經產生了,而且由於是轉換爲二進制存儲,我們很難對所有的小數進行判斷是否在存儲時丟失了精度。看下面幾個例子:

    public static void main(String[] args) {
        Float f1 = 99.99999f;
        System.out.println(f1);// 輸出結果:99.99999
        // 貌似很正常啊,其實float的內心慌的一批
        Float f2 = 99.999999f;
        System.out.println(f2);// 輸出結果:100.0
        // 此時終於暴露了吧?在存儲時就已經丟失了精度,在參與小數計算時更加暴露無遺
    }
  • float精度:小數點後6~7位
  • double精度:小數點後15~16位

丟失精度的原因經過上面的分析和例子相信大家應該很清楚了,我們按照常規流程進行二進制轉換後得到的尾數部分可能很長,但是以單精度或雙精度進行存儲時只能存儲一部分,那麼必然導致精度的丟失。

2. 解決精度不足

float和double作爲基本數據類型使用起來當然是比較方便,但是精度的問題會造成不準確,雖然我們可以通過使用保留幾位小數的方式勉強應對,但是爲了保證高精度通常會使用BigDecimal,具體用法不在此贅述,將在後續文章中說明。

3. 與長整型的比較

我們在接觸基本數據類型的時候曾經碰到過一個大哥大,曾以爲能夠裝進去很大很大的整數,畢竟是8字節的身材,但是仔細那麼一比較,存儲範圍竟然還比不過4字節的float,更不要說同等身材的double了。

  • long的存儲範圍:-2^63 ~ 2^63 - 1
  • float:-2^128 ~ 2^128
  • double:-2^1024 ~ 2^1024

以上數據只是表示一個量級,不能代表浮點數的精確範圍,不過這也足夠碾壓long類型了,以至於long類型可以隱式轉換爲float,這就解決了我們的一個疑問,爲什麼4字節的float存儲範圍比8字節的long類型還要大?自然是存儲方式不同。

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