javascript浮點數計算精度問題介紹

antzone發表於2017-04-12

大多數程式語言都有幾種數值型資料型別,但是javascript卻只有一種。你可以使用typeof 運算子檢視數字的型別。

不管是整數還是浮點數,javascript都將它們簡單地歸類為數字。

[JavaScript] 純文字檢視 複製程式碼
typeof 17; //number
typeof 98.6; //number
typeof -21.3; //number

事實上,javascript中所有的數字都是雙精度浮點數。這是由IEEE754標準制定的64位編碼數字——即“doubles”。

如果這一事實使你疑惑javascript是如何表示整數的,記住,雙精度浮點數能完美地表示高達53位精度的整數。

從–9 007 199 254 740 992(–253)到9 007 199 254 740 992(253)的所有整數都是有效的雙精度浮點數。

因此,儘管JavaScript中缺少明顯的整數型別,但是完全可以進行整數運算。 

大多數的算術運算子可以使用整數、實數或兩者的組合進行計算。

[JavaScript] 純文字檢視 複製程式碼
0.1 * 0.9; //0.19
-99 + 100; //1
21- 12.3; //8.7
2.5 /5; //0.5
21%8; //5

然而位算術運算子比較特殊。javascript不會直接將運算元作為浮點數進行運算,而是會將其隱式地轉換為32位整數後進行運算。(確切地說,它們被轉換為32位大端(big-endian)的2的補碼錶示的整數。)以按位或運算表示式為例:

[JavaScript] 純文字檢視 複製程式碼
8|1; //9

看似簡單的表示式實際上需要幾個步驟來完成運算。如前所述,javascript中的數字8和1都是雙精度浮點數。但是它們也可以表示成32位整數,即32位0、1的序列。整數8表示為32位二進位制序列如下所示:

[JavaScript] 純文字檢視 複製程式碼
00000000000000000000000000001000

自己也可以使用數字型別的toString方法來檢視:

[JavaScript] 純文字檢視 複製程式碼
(8).toString(2)  //"1000"

toString方法的引數指定了其轉換基數,此例子以基數2(即二進位制)表示。結果值省略了左端多餘的0(位),因為它們並不影響最終值。 

整數1表示為32位二進位制如下所示:

[JavaScript] 純文字檢視 複製程式碼
00000000000000000000000000000001

按位或運算表示式合併兩個位元序列。只要參與運算的兩位位元中任意一位為1,運算結果的該位就為1。以位模式表示的結果如下:

[JavaScript] 純文字檢視 複製程式碼
00000000000000000000000000001001

這個序列表示整數9。你可以使用標準的庫函式parseInt驗證,同樣以2作為基數:

[JavaScript] 純文字檢視 複製程式碼
parseInt("1000", 2); //9

(同樣,前導0位是不必要的,因為它們並不影響運算結果。) 

所有位運算子的工作方式都是相同的。它們將運算元轉換為整數,然後使用整數位模式進行運算,最後將結果轉換為標準的javascript浮點數。一般情況下,javascript引擎需要做些額外的工作來進行這些轉換。因為數字是以浮點數儲存的,必須將其轉換為整數,然後再轉換回浮點數。然而,在某些情況下,算術表示式甚至變數只能使用整數參與運算,優化編譯器有時候可以推斷出這些情形而在內部將數字以整數的方式儲存以避免多餘的轉換。


關於浮點數的最後警示是,你應該對它們保持時刻警惕。浮點數看似熟悉,但是它們是出了名的不精確。甚至一些看起來最簡單的算術運算都會產生不正確的結果。

[JavaScript] 純文字檢視 複製程式碼
0.1+0.2; 0.300000000000004

儘管64位的精度已經相當高了,但是雙精度浮點數也只能表示一組有限的數字,而不能表示所有的實數集。浮點運算只能產生近似的結果,四捨五入到最接近的可表示的實數。當你執行一系列的運算,隨著舍入誤差的積累,運算結果會越來越不精確。舍入也會使我們通常所期望的算術運算定律產生一些出人意料的偏差。例如,實數滿足結合律,這意味著,對於任意的實數x,y,z,總是滿足(x + y) + z = x + (y + z)。

然而,對於浮點數來說,卻並不總是這樣。

[JavaScript] 純文字檢視 複製程式碼
(0.1+0.2)+0.3; //0.60000000000000001
0.1+(0.2+ 0.3); //0.6

浮點數權衡了精度和效能。當我們關心精度時,要小心浮點數的侷限性。一個有效的解決方法是儘可能地採用整數值運算,因為整數在表示時不需要舍入。當進行貨幣相關的計算時,程式設計師通常會按比例將數值轉換為最小的貨幣單位來表示再進行計算,這樣就可以以整數進行計算。例如,如果上面的計算是以美元為單位,那麼,我們可以將其轉換為整數表示的美分進行計算。

[JavaScript] 純文字檢視 複製程式碼
(10+20)+30; //60
10+ (20+30); //60

對於整數運算,你不必擔心舍入誤差,但是你還是要當心所有的計算只適用於–253~253的整數。

特別說明:

[JavaScript] 純文字檢視 複製程式碼
JavaScript的數字都是雙精度的浮點數。
JavaScript中的整數僅僅是雙精度浮點數的一個子集,而不是一個單獨的資料型別
位運算子將數字視為32位的有符號整數。

相關文章