面試題 - (0.1+0.2=0.3)?

題目

0.1 + 0.2 是否等於 0.3 ?

解答

首先直接通過瀏覽器開發者模式打開控制檯看一下結果。
在這裏插入圖片描述
其實看到這個題目應該就可以猜到肯定不可能是 0.3,否則就不會出這個題目了。這個題目涉及到數學運算中的浮點運算。

0.1 轉二進制

0.1 = a * 2^-1 + b * 2^-2 + c * 2^-3 + d * 2^-4 + …

左右兩邊一直乘以2,將小數與正數分開,得到以下結果。

0 + 0.2 = a * 2^0 + b * 2^-1 + c * 2^-2 + … (a = 0)
0 + 0.4 = b * 2^0 + c * 2^-1 + d * 2^-2 + … (b = 0)
0 + 0.8 = c * 2^0 + d * 2^-1 + e * 2^-2 + … (c = 0)
1 + 0.6 = d * 2^0 + e * 2^-1 + f * 2^-2 + … (d = 1)
1 + 0.2 = e * 2^0 + f * 2^-1 + g * 2^-2 + … (e = 1)
0 + 0.4 = f * 2^0 + g * 2^-1 + h * 2^-2 + … (f = 0)
0 + 0.8 = g * 2^0 + h * 2^-1 + i * 2^-2 + … (g = 0)
1 + 0.6 = h * 2^0 + i * 2^-1 + j * 2^-2 + … (h = 1)

這個計算在不停的循環,所以 0.1 用二進制表示就是 0.00011001100110011……

0.2 轉二進制

0.2 = a * 2^-1 + b * 2^-2 + c * 2^-3 + d * 2^-4 + …

左右兩邊一直乘以2,將小數與正數分開,得到以下結果。

0 + 0.4 = a * 2^0 + b * 2^-1 + c * 2^-2 + … (a = 0)
0 + 0.8 = b * 2^0 + c * 2^-1 + d * 2^-2 + … (b = 0)
1 + 0.6 = c * 2^0 + d * 2^-1 + e * 2^-2 + … (c = 1)
1 + 0.2 = d * 2^0 + e * 2^-1 + f * 2^-2 + … (d = 1)
0 + 0.4 = e * 2^0 + f * 2^-1 + g * 2^-2 + … (e = 0)
0 + 0.8 = f * 2^0 + g * 2^-1 + h * 2^-2 + … (f = 0)
1 + 0.6 = g * 2^0 + h * 2^-1 + i * 2^-2 + … (g = 1)
1 + 0.2 = h * 2^0 + i * 2^-1 + j * 2^-2 + … (h = 1)

0.2 用二進制表示就是 0.00110011001100110……

浮點數

  1. 整數型存儲整數,浮點型存儲小數。顯示浮點數的方法有兩種,單精度和雙精度。單精度用 32 位表示,雙精度用 64 位表示。JavaScript 是遵循國際 IEEE 754 標準,將數字存儲爲雙精度浮點數,也就是用 64 位表示。

  2. 最大數和最小數一般用科學計數法來表示。比如 0.000045 可以表示爲 0.45 * 10^4。某市有 10000000 人口可以表示爲 1 * 10 ^7。科學記數法主要是爲了書寫方便。

  3. 對於二進制也是一樣,以 0.1 的二進制 0.00011001100110011…… (後面有0.1轉二進制的過程)這個數來說:
    可以表示爲:1 * 2^-4 * 1.1001100110011……
    二進制科學計數法公式爲:V = (-1)^S (1 + Fraction) 2^E

  4. 所有浮點數都可以用以上公式來表示,所以我們只需要把變化的 S,Fraction 以及 E 存儲即可。以 64 位存儲爲例,其中用 1 位存儲 S(sign,存在位 63 上,表示符號位,取 0 表示正數,1表示負數)。用 11 位存儲 E+bias,存在位 52-62 上。用 52 位存儲 Fraction,存在位 0-51 上。

綜上所述,上文 1 * 2^-4 * 1.1001100110011…… 例子中 ,
Sign(該數爲正數)爲 0。
Fraction(小數部分)爲 1001100110011……。
Exponent(指數)爲 -4。
bias (這裏是爲了輔助存儲 E,因爲要存11位,如果只是正數的話是 2^11 -1 = 2047,範圍是 0 - 2046。但是可能存在負數,所以取值範圍爲 -1023-1023,爲了不存負數,存儲的時候需要加 1023( 2^(11-1)-1=1023),取值的時候再減去1023)。所以 E+bias = -4+1023 = 1019。而 1019 的二進制爲 1111111011。
所以 0.1 用 64 位二進制表示如下:
0 01111111011 1001100110011001100110011001100110011001100110011010
同理,0.2 用 64 位二進制表示如下:
0 01111111100 1001100110011001100110011001100110011001100110011010

浮點數運算

浮點數運算有五個步驟:對階、尾數運算、規格化、舍入處理、溢出判斷。

  1. 對階就是把階碼對齊,其目的是爲了使兩個浮點數的尾數進行加減運算。比如 0.1 的二進制科學記數法是 1.1001100110011…… * 2^-4,階碼就是 -4。而 0.2 的二進制科學記數法是 1.10011001100110...* 2^-3,階碼就是 -3,兩個階碼不同,所以先調整爲相同的階碼再進行計算,調整原則是小階對大階,同時將小階碼對應的浮點數的尾數右移相應位數,以保證該浮點數的值不變。也就是 0.1 的 -4 調整爲 -3,對應變成 0.11001100110011…… * 2^-3

  2. 尾數運算
    0.1100110011001100110011001100110011001100110011001101
    + 1.1001100110011001100110011001100110011001100110011010
    ————————————————————————————————————
    10.0110011001100110011001100110011001100110011001100111

  3. 規格化
    將這個結果處理一下,即結果規格化,變成 1.0011001100110011001100110011001100110011001100110011(1) * 2^-2

  4. 舍入處理
    括號裏的 1 是計算後這個 1 超出了範圍,也就是超出了 52 位,所以要捨棄。四捨五入對應到二進制中,就是 0 舍 1 入,因爲要把括號裏的 1 丟了,這裏會進 1,結果變成 1.0011001100110011001100110011001100110011001100110100 * 2^-2

    PS:這裏不涉及溢出判斷。

所以最終的結果存成 64 位就是

0 01111111101 0011001100110011001100110011001100110011001100110100

將它轉換爲 10 進制數就得到 0.30000000000000004440892098500626

因爲兩次存儲時的精度丟失加上一次運算時的精度丟失,最終導致了 0.1 + 0.2 !== 0.3

總結

  1. 小數轉二進制可能有些麻煩,需要兩邊不斷乘2取0或1。
  2. 用科學計數法時,我們只需要存儲 S、Fraction、E(E+bais)。
  3. 用 64 位表示的二進制中,其中1位表示符號位,11位表示指數位(注意這裏是 E+bias),52位表示小數位。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章