1. 程式人生 > >Java 中 6.6f + 1.3f != 7.9f ? 到底是什麼鬼?

Java 中 6.6f + 1.3f != 7.9f ? 到底是什麼鬼?

轉載:https://mp.weixin.qq.com/s/YlQK_6TUbz5jDdGUgs-raw

今天早上有網友在群裡說感覺他自己什麼都會,我感覺他膨脹了,就給他出了一個基礎題。把他難壞了,讓我給他解釋為什麼?下面我們就一起來討論討論這個問題。

列印結果是:7.8999996。什麼個鬼,我的程式難道是個假程式嗎?

我們將 float 改為 double,在執行一下。

結果又變了,為:7.8999998569488525。

這是為什麼呢?為什麼和我預期的不一樣。

要說明這個問題,我們就要從計算機的底層的 0 和 1 說起。計算機只認識 0 和 1,所以所有的計算最終都會轉換成二進位制的計算。

float 儲存原理

CPU 表示浮點數由三部分組成 分為三個部分,符號位(sign),指數部分(exponent)和有效部分(fraction, mantissa)。 其中 float 總共佔用 32 位,符號位,指數部分,有效部分各佔 1 位,8 位,23 位。

對於實數,轉化為二進位制分為兩部分,第一部分整數部分,第二部分是小數部分。整數部分計算二進位制大家都很熟悉。

我們再看一個小數部分的計算過程。

將小數乘以2,取整數部分作為二進位制的值,然後再將小數乘以2,再取整數部分,以此往復迴圈。

你會發現,上面的計算過程會發生迴圈,迴圈體為 1001。所以 0.6 轉化為二進位制為 0.10011001…,6.6轉化為二進位制為 110.10011001… 無限迴圈。

那麼計算機該如何處理小數呢?人們是非常聰明的,所以想出了“規約化”和“指數偏移值”。

規約化

規約化,就是我們通過規約化將小數轉為規約形式,類似我們用的科學計數法,就是保證小數點前面有一個有效數字。

在二進位制裡面,就是保證整數位是一個 1。那麼 110.10011001 規約化後就為:1.1010011001*2^2

指數偏移值

是指浮點數中指數部分的值,它的值為規約形式的指數值加上某個固定的值,float 的固定值為 127,計算方法是 2^e-1 其中的 e 為儲存指數部分的位元位數,前面提到的 float 為 8 位,double 為 11 位。在這裡,因為是 2 的 2 次方,偏移值就是 127+2=129,轉換為二進位制就是 10000001

拼接

前面說了,採用二進位制科學計數法計算浮點數的,有三個部分。符號位,指數部分,有效部。

6.6 為正數,符號位為 0,指數部分為偏移值的二進位制 10000001,有效部分為規約形式的小數部分,為什麼只取小數部分?因為整數肯定是 1,去掉了不會產生誤差。我們去取小數的前 23 位即 10100110011001100110011,最後拼接到一起即 01000000110100110011001100110011。 同理,我們可以計算出 1.3 的浮點數為 00111111101001100110011001100110。

至此完成 6.6 + 1.3的過程,加減乘除方法類似,有興趣自行搜尋。