0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數


0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

前言

我們都遇到過如下計算結果:

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

為什麼會出現如此結果?難道不為 0.3 嗎?這涉及到 js 的精度問題。

首先 js 的數字類型採用基於 IEEE 754 標準來實現的(也稱為浮點數)。其選用的精度格式是:雙精度格式(64 位的二進制數)

這篇就稍稍深入瞭解下雙精度浮點數,以及有關於數 Number 的問題。

IEEE 754 標準

IEEE 二進制浮點數算術標準(IEEE 754),是最廣泛使用的浮點數運算標準,為許多 CPU 與浮點運算器所採用。

這個標準定義了表示浮點數的格式(包括負零-0)與反常值(denormal number),一些特殊數值((無窮(Inf)與非數值(NaN)),以及這些數值的“浮點數運算符”;它也指明瞭四種數值舍入規則和五種例外狀況(包括例外發生的時機與處理方式)。

規定了四種表示浮點數值的方式:單精確度(32 位)、雙精確度(64 位)、延伸單精確度(43 比特以上,很少使用)與延伸雙精確度(79 比特以上,通常以 80 位實現)

64 位的雙精度

下圖,基本解釋清楚 64 位數的組成部分:

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

64位的雙精度

64位的雙精度(維基百科)

  • sign bit(S 符號):符號位,表示正負號(0 為負數,1 為正數)
  • exponent(E 指數):表示次方數,在(二進制的)科學計數法中定義 2 的多少次冪
  • mantissa(M 尾數):表示精確度(小數部分,規範中會省略個位數上的 1 )

那麼一個雙精度值的表達式如下:

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

我知道會有很多地方看不懂,沒事,我下面來具體解釋一下。

符號位 sign

符號位很容易理解,表示整個數的正負值。

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

所以用此位來示意數的正負性。

尾數位 mantissa

尾數也被稱為規約形式的浮點數,因為在科學計數法的顯示下,分數(fraction 也是 mantissa 那個部分之一)部分最高有效為是 1 (個位數)

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

最終 mantissa 會以 000001 來示意,會被規範成 1.M 格式 ,其中 1 會被隱藏掉,所以最大是表達 53 位的數(如上圖,實際 mantissa 只有 52 位)。

指數位 exponent

科學計數法

我知道各位都是受過義務教育的,不過我真的忘記了,簡單回顧下把:

科學記數法是一種記數的方法。把一個數表示成 a 與 10 的 n 次冪相乘的形式(1≤|a|<10,n 為整數),這種記數法叫做科學記數法。


0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

那和科學計數法有什麼關係,應該注意到 指數位是 2 的 n 次冪 (為何不是 10 ,因為是二進制)。

如果我們要表達 100(2),則結果如下:

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數



指數偏移值(exponent bias)

我們瞭解科學計數法是可以表示大於 1 ,或者小於 1 的數(小數),即:通過正負指數的值來標識顯示。

由於指數位的 11 位不包括符號位,那麼為了達到這樣正負的效果,就引入了 指數的偏移值

為什麼要引入這個概念?我想了很久,以下這個例子或許會給你啟發:

指數如果是 1023 和 1024,到底哪個值誰大?

首先 11 位的指數位對應的二進制最大和最小結果值為:00000000000(0) ,11111111111(2047,2^(12-1)-1),即指數的取值範圍為:[0,2047]

並且我們知道指數具有

正負值 (來控制小數點左右移位),那麼我們按照二進制中負數的規則(取反,補位),那麼指數值為 [0,1023] 區間內為正數,[1024,2047] 內為負數(二進制中負數最高位為 1 )。

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

另外,根據 IEEE 規範, 0 和 2047 兩個最值需要做特殊用途,所以這裡移除,所以整個規範的指數取值範圍是:

[1,2046]

回到這個問題,1023 和 1024 到底誰大,按照上面區間的劃分,明顯是 1023 > 1024 (正數大於負數,但機器不那麼想)。

崩潰!就我個人理解起來就很困難,更不談實際運算了(當你看到一個大於 1023 的值,還需要引入符號位,補碼之類的計算方式)。

所以引入偏移值 bias(bias = 1023),使得整個運算簡單,好理解。

那麼 [-1022+bias,1023+bias] 等同於 [1,2046],這樣拋去了符號位的影響, 最終:1023 就變成了 0 ,1024 變成了 1 ,明顯 1 的指數值更大。

至於為何是 1023 ,我給的建議是 2046/2 (雖然這樣理解是不對的,我認為數學功底真的對編程用處很大,雖然全還給老師了),另外 32 位精度浮點數的指數偏移量是 127 。


標準(規格)和非標準(規格)

整個指數位的值分為三種情況:

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

解惑些問題

整數範圍

當尾數為標準模式時:1.M ,尾數位供 52 位,加上隱藏位 1 ,整個精度會是 53 位。

那麼整數的取值範圍是 [-2^53-1,2^53-1]

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

最小精度

在尾數的 52 位中,使得有一個最小的位定義(1.00000~ 中間 51 個 0~00001),即 2^-52 。

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

0.1+0.2 等於什麼?

按照最小精度,即打滿 52 位,那麼像 0.1 和 0.2 最終無限循環後的:

0.1+0.2≠0.3?聊聊 js 中的雙精度浮點數

最後

對於這個 IEEE 754 規範,我理解的還不是很透徹,不過對於 js 精度上的問題也算是有個初步的解答。

如果有不對之處望各位留言指正。


分享到:


相關文章: