每一個JavaScript開發(fā)者應該了解的浮點知識

2018-06-16 19:19 更新

在JavaScript開發(fā)者的開發(fā)生涯中的某些點,總會遇到奇怪的BUG——看似基礎的數(shù)學問題,但卻又覺得有些不對勁??傆幸惶?,你會被告知JavaScript中的數(shù)字實際上是浮點數(shù)。試圖了解浮點數(shù)和為什么他們?nèi)绱似婀?,迎接你的將是一片又臭又長的文章。本文的目的是給JavaScript開發(fā)者簡單講解浮點數(shù)。

本文假設讀者熟悉的用二進制表示的十進制數(shù)字(即1被寫成1b,2是10b,3是11b,4是100b……等)。為了使文章表達的更清楚,在本章中,“十進制”主要是指計算機內(nèi)部的十進制數(shù)字表示法(例如:2.718)?!岸M制”在本文中指計算機內(nèi)部的表示。書面陳述將分別被稱為“以十為底″和“以二為底″。

浮點數(shù)

什么是浮點數(shù),我們開始認為我們見過各種數(shù)字,我可可以說1是一個整數(shù),因為它沒有分數(shù)部分。

?被稱為分數(shù)。這意味著,將一平均分開為二,分數(shù)是浮點運算中一個非常重要的概念。

0.5通常被稱為一個十進制數(shù)。然而,有一個很重要的區(qū)別必須闡明——0.5實際上是分數(shù)?的十進制(以十為底)表示。本文中,我們將這種表示方法稱為點表示法。我們把0.5稱為有限表示(有限小數(shù))因為其分數(shù)表示的數(shù)字是有限的——5后面沒有其他數(shù)字。表示?的0.3333…是無限表示的例子。這個想法在我們的討論非常重要。

還存在另一種表示全部整數(shù),分數(shù)或小數(shù)的方法。你可能已經(jīng)見過。它看起來像這樣:6.022×1023(注:這是阿伏伽德羅數(shù),這是摩爾的化學溶液中的分子的數(shù)目)。它通常被稱為標準形式,或科學記數(shù)法。形式可以被抽象為像下面這樣:

D1.D2D3D4...Dp x BE

這種通用形式被稱作浮點數(shù)。

pD組成的序列——D1.D2D3D4...Dp——被稱為有效數(shù)字或尾數(shù)。p是有效數(shù)字的權重,通常稱為精度。有效數(shù)后的x是符號的一部分(本文中的乘法符號,將用*表示)。其后是基數(shù),基數(shù)后是指數(shù)。該指數(shù)可以是正或負。

浮點數(shù)的好處是它可以用來表示任何數(shù)值。例如,整數(shù)1可以表示為1.0×10^0。光的速度可以表示為2.99792458×108 m/s。1/2可以被表示為二進制形式0.1×2^0。

移除小數(shù)點

在上面的例子中,我們?nèi)匀槐A粜?shù)點(小數(shù)點在數(shù)字里面)。當用二進制表示數(shù)值的時候,這帶來了一些問題。任意給定一個浮點數(shù),比如π(PI),我們可以將其表示為一個浮點數(shù):3.14159 x 100。用二進制表示,它看起來像這樣:11.00100100 001111……假設在十六位機里表示數(shù)字,這意味著數(shù)字被放在機器里會是這樣的:11001001000011111?,F(xiàn)在的問題是:小數(shù)點應該放在哪里?這甚至不涉及指數(shù)(我們默認基數(shù)為2)。

如果數(shù)字變?yōu)?code>5.14159?整數(shù)部分將變?yōu)?code>101而不是11,增加了一位。當然,我們可以指定字段的前N位屬于整數(shù)部分(即小數(shù)點的左邊),其余屬于小數(shù)部分,但那是另一篇關于定點數(shù)的話題。

一旦我們移除小數(shù)點后,我們只有兩件東西需要記錄:指數(shù)和尾數(shù)。我們可以通過應用變換公式將小數(shù)點移除,使廣義浮點數(shù)看起來像這樣:

D1D2D3D4...Dp / (Bp-1) x BE

這就是我們得到的大多數(shù)二進制浮點數(shù)。注意,現(xiàn)在有效數(shù)是一個整數(shù)。這使得它更易于存儲一個浮點數(shù)在機器上。事實上,應用最廣泛的二進制浮點數(shù)表示方法是IEEE 754標準。

IEEE 754

JavaScript中的浮點數(shù)采用IEEE-754格式的規(guī)定。更具體的說是一個雙精度格式,這意味著每個浮點數(shù)占64位。雖然它不是二進制表示浮點數(shù)的唯一途徑,但它是目前最廣泛使用的格式。該格式用64位二進制表示像下面這樣:

你可能注意到機器表示的方法和約定俗成的書面表示一點不同。在64位中,1位用于標志位——用來表示一個數(shù)是正數(shù)還是負數(shù)。11位用于指數(shù)–這允許指數(shù)最大到1024。剩下的52位代表的尾數(shù)。如果你曾經(jīng)好奇為什么JavaScript中的某些東西如+0-0,標志位說明一切——JavaScript中的所有數(shù)字都有符號位。InfinityNaN也被編碼進浮點數(shù)——2047作為一個特殊的指數(shù)。如果尾數(shù)是0,它是一個正無窮或負無限。如果不是,那么它是NaN。

舍入誤差

有了上面對浮點數(shù)進行介紹,現(xiàn)在我們進入了一個更棘手的問題–舍入誤差。它是所有開發(fā)者使用浮點數(shù)開發(fā)的禍根,JavaScript開發(fā)者尤其如此,因為JavaScript開發(fā)者唯一可用的編號格式是浮點數(shù)。

上面提到的分數(shù)?不能在以10為底中有限表示。這實際上在任何數(shù)制中都存在。例如,在在以二為底的數(shù)字中,1 / 10不能有限表示。被表示為0.00110011001100110011……注意0011是無限重復的。這是因為這個特別的怪癖,舍入誤差造成的。

先看一個舍入誤差的例子??紤]一個最著名的無理數(shù),PI:3.141592653589793……大多數(shù)人記得前五位(3.1415)非常棒——我們將使用這個例子說明舍入誤差,因此可以計算舍入誤差:

(R - A) / Bp-1 ……其中`R`代表圓形的半徑,`A`代表一個實數(shù)。`Bp`代表以`p`為底的精度。所以謹記PI的舍入誤差:`0.00009265……`。

雖然這看起似乎不是很嚴重,讓我們試著用以二為底的數(shù)來檢驗這個想法。考慮分數(shù)1 / 10。在十進制,它被寫作0.1。在二進制中,它是:0.00011001100110011……假設我們僅保留5位尾數(shù),可以寫為0.0001。但0.0001在二進制表示法中實際是1 / 16(或0.0625)的表示!這意味著有舍入誤差為0.0375,這是相當大的。想象一下基本的加法運算,如0.1 + 0.2,答案返回0.2625!

幸運的是,浮點規(guī)范指定ECMAScript最多使用52個尾數(shù),所以舍入誤差變得很小——規(guī)范的具體細節(jié)規(guī)避了大部分的舍入誤差。因為對浮點數(shù)進行算術運算的過程中誤差會被放大,IEEE 754規(guī)范還包括用于數(shù)學運算的具體算法。

然而,應該指出的是,盡管如此,算術運算的關聯(lián)屬性(比如加法,減法,乘法和減法)不能得到保證在處理浮點數(shù)時,即使精度再高。我的意思是,((x + y)+ A + B)不一定等于((x + y)+(A + B))。

這是JavaScript開發(fā)人員的禍根。例如,在JavaScript中,0.1 + 0.2 = = = 0.3將返回假。我希望你現(xiàn)在明白這是為什么。更糟的是,事實上,舍入誤差會在連續(xù)的數(shù)學運算中增加(積累)。

在JavaScript處理浮點數(shù)

設計處理JavaScript數(shù)字的問題,已經(jīng)存在很多的建議,好壞參半。大多數(shù)這些建議都是在算數(shù)運算之前或之后完成取舍。

到目前位置我見過的寥寥無幾的建議就是把運算數(shù)全部存儲為整數(shù)(無類型),然后格式化顯示。通過一個例子可以看出,在賬戶中大量儲存的美分而不是美元(不知道舉的例子是什么賬戶)。這里有一個值得注意的問題——不是世界上所有的貨幣都是十進制的(毛里求斯幣:毛里求斯盧比是毛里求斯共和國的流通貨幣。幣值有25、50、100、200、500、1000和2000。輔幣單位為分)。同時,吐槽了日元和人名幣……。最終,你會重新創(chuàng)建浮點——有可能。

我見過處理浮點數(shù)最好的建議是使用庫,像sinfuljsmathjs。我個人比較喜歡mathjs(但實際上,任何和數(shù)學相關的我甚至不會使用JavaScript去做)。當需要任意精度數(shù)學計算的時候,BigDecimal也是非常有用的。

另一個被多次重復的建議是使用內(nèi)置的toPrecision()toFixed()方法。使用他們時最容易犯得邏輯錯誤是忘記這些方法的返回值字符串。所以如果你像下面這樣會得不到想要的結果:

function foo(x, y) {
    return x.toPrecision() + y.toPrecision()
}

> foo(0.1, 0.2)
"0.10.2"

設計內(nèi)置方法toPrecision()toFixed()的目的僅是用于顯示。謹慎使用!

結論

JavaScript中的數(shù)字是真正的浮點數(shù)。由于二進制表示的固有缺陷,以及有限的機器空間,我們不得不面對一個充滿舍入誤差的規(guī)范。本文解釋了為什么這些舍入誤差是什么和為什么。記住使用一個很棒的庫而不是自己去做一切。

原文:http://flippinawesome.org/2014/02/17/what-every-javascript-developer-should-know-about-floating-points/

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號