摘要:本文通過介紹的二進制存儲標準來理解浮點數運算精度問題,和理解對象的等屬性值是如何取值的,最后介紹了一些常用的浮點數精度運算解決方案。浮點數精度運算解決方案關于浮點數運算精度丟失的問題,不同場景可以有不同的解決方案。
本文由云+社區發表
相信大家在平常的 JavaScript 開發中,都有遇到過浮點數運算精度誤差的問題,比如 console.log(0.1+0.2===0.3)// false。在 JavaScript 中,所有的數字包括整數和小數都是用 Number 類型來表示的。本文通過介紹 Number 的二進制存儲標準來理解浮點數運算精度問題,和理解 Number 對象的 MAX_VALUE 等屬性值是如何取值的,最后介紹了一些常用的浮點數精度運算解決方案。
Number 的存儲標準JavaScript Number 采用的是 IEEE 754 定義的 64 位雙精度浮點型來表示。具體的字節分配可以先看一下引自維基百科的圖:
從上圖中可以看到,從高到低,64位被分成3段,分別是:
sign: 符號位,占 1 位;
exponent: 指數位,占 11 位;
fraction: 有效數字位,占 52 位。
指數位有 11 位,取值范圍是 0 到 2047。當指數位 e=0 或者 e=2017 時,根據有效數字位 f 是否為 0 ,具有不同的特殊含義,具體見下表:
對于常用的 normal number, 為了方便表示指數為負數的情況,所以,指數位數值大小做了一個 -1023 的偏移量。對于一個非 0 數字而言,,它的二進制的科學計數法里的第一位有效數字固定是 1。這樣,一個雙精度浮點型數字的值就是
對于 subnormal number,它可以用來表示更加接近于 0 的數,它特殊的地方是有效數字位的前面補充的是 0 而不是 1,且指數為偏移量是 -1022,所以值是:
Number 對象中的幾個屬性值知道了 Number 是如何存儲之后,Number 對象的屬性是如何取值的就明朗了。
Number.MAX_VALUE:可表示的最大的數,顯然 e 和 f 都取最大時能表示的數最大,值為
Number.MIN_VALUE:可表示的最小的正數,用最小的 subnormal number 來表示。當 e = 0 ,f 的最后一位為 1,其他為 0 時最小,值為
Number.EPSILON : 表示 1 與 Number 可表示的大于 1 的最小的浮點數之間的差值。值為
Number.MAXSAFEINTEGER:表示在 JavaScript 中最大的安全整數。可以連續且精確被表示出來的整數成為安全整數,比如 2^54 就不是個安全整數,因為它和 2^54+1 兩個數的表示是完全一樣的,e=1077,f=0。 Math.pow(2,54)===Math.pow(2,54)+1// true。整數轉化為二進制后,小數點后是不會有數字的,而用二進制的科學計數法表示時,小數點后最多保留 52 位,加上前置的一個 1,有 53 位數字,所以當一個數轉化二進制時,如果位數超過 53 位,必然會截斷末尾的部分,即導致不能精確表示,即為不安全整數。所以最小的會被截斷的整數是 100...001=2^53+1(中間有52個0)。這個數設為 X,則比 X 小的整數都能被精確表示出來,再加上“連續”這個條件,所以 X-1 不是我們要的答案,X-2 才是。 Number.MAX_SAFE_INTEGER 最終值為
Number.MINSAFEINTEGER:表示在 JavaScript 中最小的安全整數,對 Number.MAX_SAFE_INTEGER 取負值即可,值為 -9007199254740991
為什么0.1+0.2不等于0.3現在看看 console.log(0.1+0.2===0.3)// false 這個問題,數字 0.1 轉化成二進制是 0.0001100110011... 即 1.10011001...1001 2^-4 (小數部分有52位,即有13個1001循環)。由于第 53 位是 1,類似 10 進制的四舍五入,二進制是“零舍一入”,所以 0.1 的最終二進制科學計數法表示是 1.10011001...1010 2^-4,即二進制數值大小實際上是 0.000110011001...10011010。下面的代碼驗證了這個值(打印出來的值,把最末尾的0去掉了):
var a = 0.1;console.log(a.toString(2)); //0.0001100110011001100110011001100110011001100110011001101
同理十進制數字 0.2 轉化為二進制的最終值是 1.10011001...1010 2^-3 即 0.00110011...100111010;十進制 0.3 轉化位二進制的最終值是 1.00110011...0011 2^-2
var b = 0.2;console.log(b.toString(2)); //0.001100110011001100110011001100110011001100110011001101var c = 0.3;console.log(c.toString(2)); //0.010011001100110011001100110011001100110011001100110011
所以,0.1+0.2 的值即為上面 0.1 和 0.2 對應的二進制數值的相加,如下圖所示
上圖中,對所得的和,“零舍一入”保留 52 位有效小數就是最終的值:0.01001100...110100(第 53 位是 1 ,所以往前進了 1),如下代碼所示。這個值與上文中的 0.3 的最終二進制表示的值明顯不相同,即解釋了 0.1 + 0.2 不等于 0.3 的根本原因所在(實際上,這個值轉化為 10 進制約等于 0.30000000000000004)。注:打印出來的長度是 54,因為有 52 位有效小數,前面是"0.01",長度是 4,最后去掉末尾的 2 個 0,所以最后打印出來的長度是 52+4-2 = 54。
var d = 0.1 + 0.2;console.log(d.toString(2)); //0.0100110011001100110011001100110011001100110011001101console.log(d.toString(2).length); // 54浮點數精度運算解決方案
關于 js 浮點數運算精度丟失的問題,不同場景可以有不同的解決方案。 1、如果只是用來展示一個浮點數的結果,則可以借用 Number 對象的 toFixed 和 parseFloat 方法。下面代碼片段中,fixed 參數表示要保留幾位小數,可以根據實際場景調整精度。
function formatNum(num, fixed = 10) { return parseFloat(a.toFixed(fixed))}var a = 0.1 + 0.2;console.log(formatNum(a)); //0.3
2、如果需要進行浮點數的加減乘除等運算,由上文可知,在小于 Number.MAXSAFEINTEGER 范圍的整數是可以被精確表示出來的,所以可以先把小數轉化為整數,運算得到結果后再轉化為對應的小數。比如兩個浮點數的加法:
function add(num1, num2) { var decimalLen1 = (num1.toString().split(".")[1] || "").length; //第一個參數的小數個數 var decimalLen2 = (num2.toString().split(".")[1] || "").length; //第二個參數的小數個數 var baseNum = Math.pow(10, Math.max(decimalLen1, decimalLen2)); return (num1 * baseNum + num2 * baseNum) / baseNum;}console.log(add(0.1 , 0.2)); //0.3
參考資料
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...
此文已由作者授權騰訊云+社區發布
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108833.html
摘要:也就是說不僅是會產生這種問題,只要是采用的浮點數編碼方式來表示浮點數時,則會產生這類問題。到這里我們都理解只要采取的浮點數編碼的語言均會出現上述問題,只是它們的標準類庫已經為我們提供了解決方案而已。 Brief 一天有個朋友問我JS中計算0.7 * 180怎么會等于125.99999999998,坑也太多了吧!那時我猜測是二進制表示數值時發生round-off error所導致,但并不...
摘要:表示正數,表示負數。是一個無符號整數,因為長度是位,取值范圍是。浮點數具體數值的實際表示。例如對于單精度浮點數,指數部分實際最小值是,對應的尾數部分從一直到,相鄰兩小浮點數之間的距離都是而與最近的浮點數即最小的非規約數也是。 二進制表示小數 例如用二進制表示 0.8125 0.8125 0.8125*2 = 1.625 取整為 1 0.625*2=1.25 取整為 1 0.25*2=0...
摘要:推導為何等于在中所有數值都以標準的雙精度浮點數進行存儲的。先來了解下標準下的雙精度浮點數。精度位總共是,因為用科學計數法表示,所以首位固定的就沒有占用空間。驗證完成的最大安全數是如何來的根據雙精度浮點數的構成,精度位數是。 閱讀完本文可以了解到 0.1 + 0.2 為什么等于 0.30000000000000004 以及 JavaScript 中最大安全數是如何來的。 十進制小數轉為二...
摘要:而的浮點數設置的偏移值是,因為指數域表現為一個非負數,位,所以,實際的,所以。這是因為它們在轉為二進制時要舍入部分的不同可能造成的不同舍 IEEE 754 表示:你盡管抓狂、罵娘,但你能完全避開我,算我輸。 一、IEEE-754浮點數捅出的那些婁子 首先我們還是來看幾個簡單的問題,能說出每一個問題的細節的話就可以跳過了,而如果只能泛泛說一句因為IEEE754浮點數精度問題,那么下文還是...
閱讀 2571·2021-11-22 09:34
閱讀 932·2021-11-19 11:34
閱讀 2801·2021-10-14 09:42
閱讀 1472·2021-09-22 15:27
閱讀 2385·2021-09-07 09:59
閱讀 1731·2021-08-27 13:13
閱讀 3432·2019-08-30 11:21
閱讀 771·2019-08-29 18:35