【轉載】JS Number型別數字位數及IEEE754標準

卡卡西村長發表於2018-11-29

JS的基礎型別Number,遵循 IEEE 754 規範,採用雙精度儲存(double precision),佔用 64 bit。如圖

 

意義

  • 1位用來表示符號位
  • 11位用來表示指數
  • 52位表示尾數

 

浮點數,比如

1
2
0.1 >> 0.0001 1001 1001 1001…(1001無限迴圈)
0.2 >> 0.0011 0011 0011 0011…(0011無限迴圈)

此時只能模仿十進位制進行四捨五入了,但是二進位制只有 0 和 1 兩個,於是變為 0 舍 1 入。這即是計算機中部分浮點數運算時出現誤差,丟失精度的根本原因。

 

大整數的精度丟失和浮點數本質上是一樣的,尾數位最大是 52 位,因此 JS 中能精準表示的最大整數是 Math.pow(2, 53),十進位制即 9007199254740992。

大於 9007199254740992 的可能會丟失精度

1
2
3
9007199254740992     >> 10000000000000...000 // 共計 53 個 0
9007199254740992 + 1 >> 10000000000000...001 // 中間 52 個 0
9007199254740992 + 2 >> 10000000000000...010 // 中間 51 個 0

 

實際上

1
2
3
4
9007199254740992 + 1 // 丟失
9007199254740992 + 2 // 未丟失
9007199254740992 + 3 // 丟失
9007199254740992 + 4 // 未丟失

 解決方法:

對於整數,前端出現問題的機率可能比較低,畢竟很少有業務需要需要用到超大整數,只要運算結果不超過 Math.pow(2, 53) 就不會丟失精度。

對於小數,前端出現問題的機率還是很多的,尤其在一些電商網站涉及到金額等資料。解決方式:把小數放到位整數(乘倍數),再縮小回原來倍數(除倍數)

1
2
// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true

以下摘自百度:

 

IEEE 754標準規定了什麼IEEE 754 規定:

a) 兩種基本浮點格式:單精度和雙精度。IEEE單精度格式具有24位有效數字,並總共佔用32 位。IEEE雙精度格式具有53位有效數字精度,並總共佔用64位。 說明:基本浮點格式是固定格式,相對應的十進位制有效數字分別為7位和17位。基本浮點格式對應的C/C++型別為float和double。

b) 兩種擴充套件浮點格式:單精度擴充套件和雙精度擴充套件。此標準並未規定擴充套件格式的精度和大小,但它指定了最小精度和大小。例如,IEEE 雙精度擴充套件格式必須至少具有64位有效數字,並總共佔用至少79 位。 說明:雖然IEEE 754標準沒有規定具體格式,但是實現者可以選擇符合該規定的格式,一旦實現,則為固定格式。例如:x86 FPU是80位擴充套件精度,而Intel安騰FPU是82位擴充套件精度,都符合IEEE 754標準的規定。C/C++對於擴充套件雙精度的相應型別是long double,但是,Microsoft Visual C++ 6.0版本以上的編譯器都不支援該型別,long double和double一樣,都是64位基本雙精度,只能用其它C/C++編譯器或組合語言。

c) 浮點運算的準確度要求:加、減、乘、除、平方根、餘數、將浮點格式的數舍入為整數值、在不同浮點格式之間轉換、在浮點和整數格式之間轉換以及比較。求餘和比較運算必須精確無誤。其他的每種運算必須向其目標提供精確的結果,除非沒有此類結果,或者該結果不滿足目標格式。對於後一種情況,運算必須按照下面介紹的規定舍入模式的規則對精確結果進行最低限度的修改,並將經過此類修改的結果提供給運算的目標。 說明:IEEE754沒有規定基本算術運算(+、-、×、/ 等)的結果必須精確無誤,因為對於IEEE 754的二進位制浮點數格式,由於浮點格式長度固定,基本運算的結果幾乎不可能精確無誤。這裡用三位精度的十進位制加法來說明: 例1:a = 3.51,b = 0.234,求a+b = ? a與b都是三位有效數字,但是,a+b的精確結果為3.744,是四位有效數字,對於該浮點格式只有三位精度,a+b的結果無法精確表示,只能近似表示,具體運算結果取決於舍入模式(見舍入模式的說明)。同理,由於浮點格式固定,對於其他基本運算,結果也幾乎無法精確表示。

d) 在十進位制字串和兩種基本浮點格式之一的二進位制浮點數之間進行轉換的準確度、單一性和一致性要求。對於在指定範圍內的運算元,這些轉換必須生成精確的結果(如果可能的話),或者按照規定舍入模式的規則,對此類精確結果進行最低限度的修改。對於不在指定範圍內的運算元,這些轉換生成的結果與精確結果之間的差值不得超過取決於舍入模式的指定誤差。

說明:這一條規定是針對十進位制字串表示的資料與二進位制浮點數之間相互轉換的規定,也是一般程式設計者最容易產生錯覺的事情。因為人最熟悉的是十進位制,以為對於任意十進位制數,二進位制都應該能精確表示,其實不然。本文主要目的就是揭密二進位制浮點數所能夠精確表示的十進位制數,如果你以前沒有想過這個問題,絕對讓你吃驚。賣個關子先!

e) 五種型別的IEEE 浮點異常,以及用於向使用者指示發生這些型別異常的條件。五種型別的浮點異常是:無效運算、被零除、上溢、下溢和不精確。

說明:關於浮點異常,見Kahan教授的《Lecture Notes on IEEE 754》,這裡我就不浪費口水了。

f) 四種舍入方向:向最接近的可表示的值;當有兩個最接近的可表示的值時首選"偶數"值;向負無窮大(向下);向正無窮大(向上)以及向0(截斷)。

說明:舍入模式也是比較容易引起誤解的地方之一。我們最熟悉的是四捨五入模式,但是,IEEE 754標準根本不支援,它的預設模式是最近舍入(Round to Nearest),它與四捨五入只有一點不同,對.5的舍入上,採用取偶數的方式。舉例比較如下:

最近舍入模式:

Round(0.5) = 0;

Round(1.5) = 2;

Round(2.5) = 2;

四捨五入模式:

Round(0.5) = 1;

Round(1.5) = 2;

Round(2.5) = 3

主要理由:由於字長有限,浮點數能夠精確表示的數是有限的,因而也是離散的。在兩個可以精確表示的相鄰浮點數之間,必定存在無窮多實數是IEEE浮點數所無法精確表示的。如何用浮點數表示這些數,IEEE 754的方法是用距離該實數最近的浮點數來近似表示。但是,對於.5,它到0和1的距離是一樣近,偏向誰都不合適,四捨五入模式取1,雖然銀行在計算利息時,願意多給0.5分錢,但是,它並不合理。例如:如果在求和計算中使用四捨五入,一直算下去,誤差有可能越來越大。機會均等才公平,也就是向上和向下各佔一半才合理,在大量計算中,從統計角度來看,高一位分別是偶數和奇數的概率正好是50% : 50%。至於為什麼取偶數而不是奇數,大師Knuth有一個例子說明偶數更好,於是一錘定音。最近舍入模式在C/C++中沒有相應的函式,當然,IEEE754以及x86 FPU的預設舍入模式是最近舍入,也就是每次浮點計算結果都採用最近舍入模式,除非用程式顯式設定為其它三種舍入模式。 另外三種舍入模式,簡要說明。

向0(截斷)舍入:

C/C++的型別轉換。

(int) 1.324 = 1

(int) -1.324 = -1

向負無窮大(向下)舍入:

C/C++函式floor()。例如:

floor(1.324) = 1

floor(-1.324) = -2

向正無窮大(向上)舍入:

C/C++函式ceil()

ceil(1.324) = 2

Ceil(-1.324) = -1

後兩種舍入方法據說是為了數值計算中的區間演算法,但很少聽說哪個商業軟體使用區間演算法。

相關文章