組成原理|為什麼計算機中0.3 + 0.6 等於 0.899999999...?

fishers發表於2019-06-19

浮點數的不精確性

開啟瀏覽器控制檯 輸入 0.3 + 0.6 ,結果輸出了0.8999999,不相信你可以按F12開啟控制檯試一試O(∩_∩)O
組成原理|為什麼計算機中0.3 + 0.6 等於 0.899999999...?

這是為什麼?
我們先看下面的介紹,最後文末會給出答案。

定點數的表示

使用BCD編碼:用二進位制來表示十進位制的編碼方式

編碼過程:我們用 4 個位元來表示 0~9 的整數,那麼 32 個位元就可以表示8個這樣的數
然後我們把最右邊的 2 個 0~9 的整數,當成小數部分;
把左邊 6 個 0~9 的整數,當成整數部分。
這樣,我們就可以用 32 個位元,來表示從 0 到 999999.99 這樣 1 億個實數了。

適用途徑:銀行、商家精確到“分0.01 ”的交易。
缺點:浪費位元啊,而且這樣的表示方式沒辦法同時表示很大的數字和很小的數字。

浮點數的表示

IEEE的標準,它定義了兩個基本的格式:float單精度 和 double雙精度

下面是表示方法:

1.符號為表示正負0和1。
2.指數位,因為我們也需要表示很小的數,指數位置需要負數,所以我們在這裡用 1~254 對映到 -126~127 這 254個有正有負的數
3.有效數位,是一個 23 個位元組成的有效數位。我們用f來表示。

組成原理|為什麼計算機中0.3 + 0.6 等於 0.899999999...?

組成原理|為什麼計算機中0.3 + 0.6 等於 0.899999999...?

舉了栗子:
組成原理|為什麼計算機中0.3 + 0.6 等於 0.899999999...?

浮點數相加的精度誤差

栗子1:讓一個值為 2000 萬的 32 位浮點數和 1 相加,你會發現,+1 這個過程因為精度損失,被“完全拋棄”了。

float a = 20000000.0f;
float b = 1.0f;
float sum = a + b;
print sum //發現sum的值還是2000萬,而不是 200000001

為什麼出現上述情況,因為兩個浮點數相加的運算是需要移位的,
過大或者過小的數移位之後可能會出現有效位數消失的情況,此部分內容可以搜尋相關資料:浮點數相加運算過程、浮點數的二進位制表示

栗子2:將一個等於1.0的浮點數累加迴圈加2000萬次,發現結果是1600萬左右,你可以用java實現下面程式碼,看看結果~~

為什麼?也是浮點數加法產生的精度誤差


    float sum = 0.0f;
    for (int i = 0; i < 20000000; i++) {
        float x = 1.0f;
        sum += x;       
    }
    print sum // sum 約等於1.6777216E7

解決方案:Kahan Summation 演算法


    float sum = 0.0f;
    float c = 0.0f;
    for (int i = 0; i < 20000000; i++) {
        float x = 1.0f;
        float y = x - c;
        float t = sum + y;
        c = (t-sum)-y;
        sum = t;        
    }
   print sum;//2000萬

在每次的計算過程中,都用一次減法,把當前加法計算中損失的精度記錄下來然後在後面的迴圈中,把這個精度損失放在要加的小數上,再做一次運算。
該演算法的數學證明參見:Wikipedia 連結

所以回到文章開頭的問題,0.3 + 0.6 為什麼不等於0.9,
就是因為計算機用浮點數表示法來表示浮點數,只能精確表示 2^x (2的x次方)這種數0.3、0.6不能精確表示出來,例如0.5是2^(-1)是可以被精確表示的。

相關文章