每一個JavaScript開發者應該瞭解的浮點知識
在JavaScript開發者的開發生涯中的某些點,總會遇到奇怪的BUG——看似基礎的數學問題,但卻又覺得有些不對勁。總有一天,你會被告知JavaScript中的數字實際上是浮點數。試圖瞭解浮點數和為什麼他們如此奇怪,迎接你的將是一片又臭又長的文章。本文的目的是給JavaScript開發者簡單講解浮點數。
本文假設讀者熟悉的用二進位制表示的十進位制數字(即1被寫成1b,2是10b,3是11b,4是100b……等)。為了使文章表達得更清楚,在本章中,“十進位制”主要是指計算機內部的十進位制數字表示法(例如:2.718)。“二進位制”在本文中指計算機內部的表示。書面陳述將分別被稱為“以十為底″和“以二為底″。
浮點數
什麼是浮點數,我們開始認為我們見過各種數字,我可可以說1
是一個整數,因為它沒有分數部分。
½被稱為分數。這意味著,將一平均分開為二,分數是浮點運算中一個非常重要的概念。
0.5
通常被稱為一個十進位制數。然而,有一個很重要的區別必須闡明——0.5
實際上是分數½的十進位制(以十為底)表示。本文中,我們將這種表示方法稱為點表示法。我們把0.5
稱為有限表示(有限小數)因為其分數表示的數字是有限的——5
後面沒有其他數字。表示⅓的0.3333
…是無限表示的例子。這個想法在我們的討論非常重要。
還存在另一種表示全部整數,分數或小數的方法。你可能已經見過。它看起來像這樣:6.022×1023(注:這是阿伏伽德羅數,這是摩爾的化學溶液中的分子的數目)。它通常被稱為標準形式,或科學記數法。形式可以被抽象為像下面這樣:
D1.D2D3D4...Dp x BE
這種通用形式被稱作浮點數。
由p
和D
組成的序列——D1.D2D3D4...Dp
——被稱為有效數字或尾數。p
是有效數字的權重,通常稱為精度。有效數後的x
是符號的一部分(本文中的乘法符號,將用*表示)。其後是基數,基數後是指數。該指數可以是正或負。
浮點數的好處是它可以用來表示任何數值。例如,整數1可以表示為1.0×10^0
。光的速度可以表示為2.99792458×108 m/s
。1/2可以被表示為二進位制形式0.1×2^0
。
移除小數點
在上面的例子中,我們仍然保留小數點(小數點在數字裡面)。當用二進位制表示數值的時候,這帶來了一些問題。任意給定一個浮點數,比如π
(PI),我們可以將其表示為一個浮點數:3.14159 x 100
。用二進位制表示,它看起來像這樣:11.00100100 001111……
假設在十六位機裡表示數字,這意味著數字被放在機器裡會是這樣的:11001001000011111
。現在的問題是:小數點應該放在哪裡?這甚至不涉及指數(我們預設基數為2)。
如果數字變為5.14159
?整數部分將變為101
而不是11
,增加了一位。當然,我們可以指定欄位的前N位屬於整數部分(即小數點的左邊),其餘屬於小數部分,但那是另一篇關於定點數的話題。
一旦我們移除小數點後,我們只有兩件東西需要記錄:指數和尾數。我們可以通過應用變換公式將小數點移除,使廣義浮點數看起來像這樣:
D1D2D3D4...Dp / (Bp-1) x BE
這就是我們得到的大多數二進位制浮點數。注意,現在有效數是一個整數。這使得它更易於儲存一個浮點數在機器上。事實上,應用最廣泛的二進位制浮點數表示方法是IEEE 754標準。
IEEE 754
JavaScript中的浮點數採用IEEE-754格式的規定。更具體的說是一個雙精度格式,這意味著每個浮點數佔64位。雖然它不是二進位制表示浮點數的唯一途徑,但它是目前最廣泛使用的格式。該格式用64位二進位制表示像下面這樣:
你可能注意到機器表示的方法和約定俗成的書面表示一點不同。在64位中,1位用於標誌位——用來表示一個數是正數還是負數。11位用於指數–這允許指數最大到1024
。剩下的52位代表的尾數。如果你曾經好奇為什麼JavaScript中的某些東西如+0
和 -0
,標誌位說明一切——JavaScript中的所有數字都有符號位。Infinity
和NaN
也被編碼進浮點數——2047
作為一個特殊的指數。如果尾數是0
,它是一個正無窮或負無限。如果不是,那麼它是NaN
。
舍入誤差
有了上面對浮點數進行介紹,現在我們進入了一個更棘手的問題–舍入誤差。它是所有開發者使用浮點數開發的禍根,JavaScript開發者尤其如此,因為JavaScript開發者唯一可用的編號格式是浮點數。
上面提到的分數⅓不能在以10為底中有限表示。這實際上在任何數制中都存在。例如,在在以二為底的數字中,1 / 10不能有限表示。被表示為0.00110011001100110011……
注意0011
是無限重複的。這是因為這個特別的怪癖,舍入誤差造成的。
先看一個舍入誤差的例子。考慮一個最著名的無理數,PI:3.141592653589793……
大多數人記得前五位(3.1415
)非常棒——我們將使用這個例子說明舍入誤差,因此可以計算舍入誤差:
(R - A) / Bp-1
……其中R
代表圓形的半徑,A
代表一個實數。Bp
代表以p
為底的精度。所以謹記PI的舍入誤差:0.00009265……
。
雖然這看起似乎不是很嚴重,讓我們試著用以二為底的數來檢驗這個想法。考慮分數1 / 10。在十進位制,它被寫作0.1
。在二進位制中,它是:0.0011001100110011……
假設我們僅保留5位尾數,可以寫為0.0001
。但0.0001
在二進位制表示法中實際是1 / 16(或0.0625
)的表示!這意味著有舍入誤差為0.0375
,這是相當大的。想象一下基本的加法運算,如0.1 + 0.2
,答案返回0.2625
!
幸運的是,浮點規範指定ECMAScript最多使用52個尾數,所以舍入誤差變得很小——規範的具體細節規避了大部分的舍入誤差。因為對浮點數進行算術運算的過程中誤差會被放大,IEEE 754規範還包括用於數學運算的具體演算法。
然而,應該指出的是,儘管如此,算術運算的關聯屬性(比如加法,減法,乘法和減法)不能得到保證在處理浮點數時,即使精度再高。我的意思是,((x + y)+ A + B)
不一定等於((x + y)+(A + B))
。
這是JavaScript開發人員的禍根。例如,在JavaScript中,0.1 + 0.2 = = = 0.3
將返回假。我希望你現在明白這是為什麼。更糟的是,事實上,舍入誤差會在連續的數學運算中增加(積累)。
在JavaScript處理浮點數
設計處理JavaScript數字的問題,已經存在很多的建議,好壞參半。大多數這些建議都是在算數運算之前或之後完成取捨。
到目前位置我見過的寥寥無幾的建議就是把運算數全部儲存為整數(無型別),然後格式化顯示。通過一個例子可以看出,在賬戶中大量儲存的美分而不是美元(不知道舉的例子是什麼賬戶)。這裡有一個值得注意的問題——不是世界上所有的貨幣都是十進位制的(模里西斯幣:模里西斯盧比是模里西斯共和國的流通貨幣。幣值有25、50、100、200、500、1000和2000。輔幣單位為分)。同時,吐槽了日元和人民幣……。最終,你會重新建立浮點——有可能。
我見過處理浮點數最好的建議是使用庫,像sinfuljs或mathjs。我個人比較喜歡mathjs(但實際上,任何和數學相關的我甚至不會使用JavaScript去做)。當需要任意精度數學計算的時候,BigDecimal也是非常有用的。
另一個被多次重複的建議是使用內建的toPrecision()
和toFixed()
方法。使用他們時最容易犯得邏輯錯誤是忘記這些方法的返回值字串。所以如果你像下面這樣會得不到想要的結果:
function foo(x, y) {
return x.toPrecision() + y.toPrecision()
}
> foo(0.1, 0.2)
"0.10.2"
設計內建方法toPrecision()
和toFixed()
的目的僅是用於顯示。謹慎使用!
結論
JavaScript中的數字是真正的浮點數。由於二進位制表示的固有缺陷,以及有限的機器空間,我們不得不面對一個充滿舍入誤差的規範。本文解釋了為什麼這些舍入誤差是什麼和為什麼。記住使用一個很棒的庫而不是自己去做一切。
注
原文:http://flippinawesome.org/2014/02/17/what-every-javascript-developer-should-know-about-floating-points/
Q群推薦
- CSS家園188275051,CSS開發者的天堂,歡迎有興趣的同學加入
- JavaScript家園159973528,JavaScript開發者的天堂,歡迎有興趣的同學加入
- GitHub家園225932282,GitHub愛好者的天堂,歡迎有興趣的同學加入
- 碼農之家203145707,碼農的天堂,歡迎有興趣的同學加入
- 畢業設計無憂群—IT 307682157,代做畢業設計,歡迎加入
相關文章
- 每個MySQL開發者都應該瞭解的10個技巧MySql
- 每個前端工程師都應該瞭解的圖片知識前端工程師
- 每個程式設計師都應該瞭解的硬體知識程式設計師
- 每個程式設計師都應該瞭解的記憶體知識程式設計師記憶體
- 每個Android開發者都應該瞭解的資源列表Android
- 每個 Web 開發者應該知道的 jQuery i18n 知識WebjQuery
- 每個開發者都應該知道的33個JavaScript概念JavaScript
- 每個Web開發者都應該知道的關 URL編碼的知識Web
- 每個程式設計師都應該瞭解的“虛擬記憶體”知識程式設計師記憶體
- 每個 JavaScript 開發者都該瞭解的 ES2018 新特性JavaScript
- 每個JavaScript開發人員都應該瞭解UnicodeJavaScriptUnicode
- 每個 Java 開發者都應該知道的 5 個註解Java
- 建立索引,這些知識應該瞭解索引
- 前端開發者應當瞭解的 Web 快取知識前端Web快取
- 每個人都應該瞭解的金融小知識 — 利率計算 (含一道碼農面試題)面試題
- 每個人都應該瞭解的金融小知識 -- 利率計算 (含一道碼農面試題)面試題
- 每個安卓開發初學者應該瞭解的 12 個技巧安卓
- 為什麼每個程式設計師都應該懂點前端知識?程式設計師前端
- 每個開發人員都應該知道的WebSockets知識Web
- 每個PHP開發者都應該看的書PHP
- 你所不瞭解的javascript操作DOM的細節知識點(一)JavaScript
- 每個工程師都應該瞭解的:聊聊冪等工程師
- 建立維基百科詞條之前應該瞭解的幾點知識
- 為SSD程式設計(6):總結—每個程式設計師都應該瞭解的固態硬碟知識程式設計師硬碟
- 你應該瞭解的 5 個 JavaScript 除錯技巧JavaScript除錯
- [翻譯]每一個計算機專業的學生應該知道的知識(一)計算機
- 初識python你應該知道的6個知識點!Python
- 每個程式設計師都應該瞭解的一件事程式設計師
- 每個前端工程師都應該瞭解的HTML5.2前端工程師HTML
- 程式設計師都應該瞭解哪些安全知識程式設計師
- 每個Java開發者都應該知道的5個JDK工具JavaJDK
- 回溯法應該知道的知識點
- 關於JavaScript的作用域你應該瞭解的那點事!JavaScript
- 每個Javascript開發者都應當知道的那些事JavaScript
- 浮點數小知識點
- 開發者應該瞭解的API技術清單!API
- 每個 Java 開發者應該知道(並愛上)的 8 個工具Java
- 每個Android開發者應該知道的6個SDK和APIAndroidAPI