JavaScript 中小數和大整數的精度丟失

來源:互聯網
上載者:User

原文:http://demon.tw/copy-paste/javascript-precision.html

JavaScript 中小數和大整數的精度丟失

標籤: JavaScript, JS, JScript

標題: JavaScript 中小數和大整數的精度丟失
作者: Demon
連結: http://demon.tw/copy-paste/javascript-precision.html
著作權: 本部落格的所有文章,都遵守“署名-非商業性使用-相同方式共用 2.5 中國大陸”協議條款。

先來看兩個問題:

0.1 + 0.2 == 0.3; // false9999999999999999 == 10000000000000000; // true

第一個問題是小數的精度問題,在業界不少部落格裡已有討論。第二個問題,去年公司有個系統的資料庫在做資料訂正時,發現有部分資料重複的詭異現象。本文將從規範出發,對上面的問題做個小結。

最大整數

JavaScript 中的數字是用 IEEE 754 雙精確度 64 位元浮點數 來儲存的,其格式為:

s x m x 2^e

s 是符號位,表示正負。 m 是尾數,有 52 bits. e 是指數,有 11 bits. 在 ECMAScript 規範 裡有給出 e 的範圍為 [-1074, 971]. 這樣,很容易推匯出 JavaScript 能表示的最大整數為:

1 x (2^53 - 1) x 2^971 = 1.7976931348623157e+308

這個值正是 Number.MAX_VALUE

同理可推匯出 Number.MIN_VALUE 的值為:

1 x 1 x 2^(-1074) = 5e-324

注意 MIN_VALUE 表示最接近 0 的正數,而不是最小的數。最小的數是 -Number.MAX_VALUE

小數的精度丟失

十進位 0.1 的二進位為 0.0 0011 0011 0011 … (迴圈 0011)十進位 0.2 的二進位為 0.0011 0011 0011 … (迴圈 0011)0.1 + 0.2 相加可表示為:   e = -4; m = 1.10011001100...1100(52 位) + e = -3; m = 1.10011001100...1100(52 位)---------------------------------------------   e = -3; m = 0.11001100110...0110 + e = -3; m = 1.10011001100...1100---------------------------------------------   e = -3; m = 10.01100110011...001--------------------------------------------- = 0.01001100110011...001 = 0.30000000000000004(十進位)

根據上面的演算,還可以得出一個結論:當十進位小數的二進位表示的有限數字不超過 52 位時,在 JavaScript 裡是可以精確儲存的。比如:

0.05 + 0.005 == 0.055 // true

進一步的規律,比如:

0.05 + 0.2 == 0.25 // true0.05 + 0.9 == 0.95 // false

需要考慮 IEEE 754 的 Rounding modes, 有興趣的可進一步研究。

大整數的精度丟失

這個問題鮮有人提及。首先得弄清楚問題是什麼:

1. JavaScript 能儲存的最大整數是什嗎?

該問題前面已回答,是 Number.MAX_VALUE, 非常大的一個數。

2. JavaScript 能儲存的且不丟失精度的最大整數是什嗎?

根據 s x m x 2^e, 符號位取正,52 位尾數全填充 1, 指數 e 取最大值 971, 顯然,答案依舊是 Number.MAX_VALUE.

我們的問題究竟是什麼呢?回到起始程式碼:

9999999999999999 == 10000000000000000; // true

很明顯,16 個 9 還遠遠小於 308 個 10. 這個問題與 MAX_VALUE 沒什麼關係,還得歸屬到尾數 m 只有 52 位上來。

可以用代碼來描述:

var x = 1; // 為了減少運算量,初始值可以設大一點,比如 Math.pow(2, 53) - 10while(x != x + 1) x++;// x = 9007199254740992 即 2^53

也就是說,當 x 小於等於 2^53 時,可以確保 x 的精度不會丟失。當 x 大於 2^53 時,x 的精度有可能會丟失。比如:

x 為 2^53 + 1 時,其二進位表示為:10000000000...001 (中間共有 52 個 0)用雙精確度浮點數儲存時:e = 1; m = 10000..00(共 52 個 0,其中 1 是 hidden bit)顯然,這和 2^53 的儲存是一樣的。

按照上面的思路可以推出,對於 2^53 + 2, 其二進位為 100000…0010(中間 51 個 0),也是可以精確儲存的。

規律:當 x 大於 2^53 且二進位有效位元大於 53 位時,就會存在精度丟失。這和小數的精度丟失本質上是一樣的。

hidden bit 可參考:A tutorial about Java double type.

小結

小數和大整數的精度丟失,並不僅僅在 JavaScript 中存在。嚴格來說,使用了IEEE 754 浮點數格式來儲存浮點類型的任何程式設計語言(C/C++/C#/Java 等等)都存在精度丟失問題。在 C#、Java 中,提供了 Decimal、BigDecimal 封裝類來進行相應的處理,才避開了精度丟失。

註:ECMAScript 規範中,已有 decimal proposal,但目前尚未被正式採納。

最後考考大家:

Number.MAX_VALUE + 1 == Number.MAX_VALUE;Number.MAX_VALUE + 2 == Number.MAX_VALUE;...Number.MAX_VALUE + x == Number.MAX_VALUE;Number.MAX_VALUE + x + 1 == Infinity;...Number.MAX_VALUE + Number.MAX_VALUE == Infinity;// 問題:// 1. x 的值是什嗎?// 2. Infinity - Number.MAX_VALUE == x + 1; 是 true 還是 false ?
參考資料
  • Wikipedia: Float point
  • ES5: The Number Type
  • Javascript – MAX_INT: Number Limits
  • Maxinum number
  • 關於 JavaScript 計算精度丟失的問題

原文連結:JavaScript 中小數和大整數的精度丟失

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.