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