溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript中Number類型常見誤區(qū)如何解決

發(fā)布時間:2022-10-09 17:40:32 來源:億速云 閱讀:134 作者:iii 欄目:web開發(fā)

這篇“JavaScript中Number類型常見誤區(qū)如何解決”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“JavaScript中Number類型常見誤區(qū)如何解決”文章吧。

在 JavaScript 中數(shù)值只有一種,即 Number 類型,內(nèi)部表示為雙精度浮點型,即其他語言中的 double 類型,所以在 JavaScript 中實際上是沒有整數(shù)類型的,數(shù)值都是按浮點數(shù)來處理的,存儲方法相同,遵循 IEEE 754 國際標(biāo)準(zhǔn)。因此,在 JavaScript 中 3 和 3.0 被視為同一個值:

3.0 === 3 // true

對于整數(shù)情況,能夠準(zhǔn)確計算的整數(shù)范圍為在?253-2^{53}?253 ~ 2532^{53}253 之間,不包含兩個端點,只要在這個范圍內(nèi)整數(shù)可以放心使用。除了十進制以外整數(shù)還可以通過八進制或十六進制的字面值來表示,其中八進制字面值的第一位必須是零,其次是八進制數(shù)字序列(0 ~ 7),如果字面值中的數(shù)值超出范圍,那么前導(dǎo)零將被忽略,后面的數(shù)值被當(dāng)作十進制解析,這兒需要注意在嚴(yán)格模式中八進制的這種表示會報錯,ES6中進一步明確,八進制的表示要使用前綴0o,示例:

(function(){
  console.log(0o11 === 011)
})()
// true
// 嚴(yán)格模式
(function(){
  'use strict';
  console.log(0o11 === 011)
})()
// Uncaught GyntaxError

十六進制字面值前兩位必須是 0x,后跟任何十六進制數(shù)字(0 ~ 9以及A ~ F),其中 A ~ F 可以大寫,也可以小寫。ES6中又?jǐn)U展了二進制的寫法,使用前綴0b(或0B)。

前面我們對 JavaScript 運行時中的 Number 類型進行了簡單介紹,接下來正式開始介紹這些常見問題,不過首先我們需要了解 Number 類型的數(shù)據(jù)存儲方式:

一、存儲方式

JavaScript 中的 Number 類型使用的是雙精度浮點型,即其他語言中的 double 類型,雙精度浮點數(shù)使用 8 個字節(jié)即 64bit 來進行存儲,現(xiàn)代計算機中浮點數(shù)大多是以國際標(biāo)準(zhǔn) IEEE 754 來存儲,存儲過程分兩步,

  • 把浮點數(shù)轉(zhuǎn)換為對應(yīng)的二進制數(shù),并用科學(xué)計數(shù)法表示

  • 將轉(zhuǎn)換之后的數(shù)通過 IEEE 754 標(biāo)準(zhǔn)表示成真正會在計算機存儲的值。

根據(jù) IEEE 754 標(biāo)準(zhǔn)任何一個二進制浮點數(shù) V 都可以表示成:

JavaScript中Number類型常見誤區(qū)如何解決

JavaScript中Number類型常見誤區(qū)如何解決

舉個例子,十進制的 5.0,寫成二進制是 101.0,相當(dāng)于 1.01?221.01 * 2^21.01?22,其中 S=0,M=1.01,E=2。

IEEE 754規(guī)定對于32位浮點數(shù)最高1位是符號位S,接下來8位是指數(shù)E,剩下的23位為有效數(shù)字M,具體如下圖所示:

JavaScript中Number類型常見誤區(qū)如何解決

對于64位的浮點數(shù)最高1位是符號位S,接下來11位是指數(shù)E,剩下的52位是有效數(shù)字M,具體如下圖所示:

JavaScript中Number類型常見誤區(qū)如何解決

注意:IEEE754 對于有效數(shù)字M和指數(shù)E還有一些特別的規(guī)定。

前面說過,1 <= M < 2,也就是說 M 總是可以寫成 1.xxxxxxx 的形式,其中 xxxxxxx 表示效數(shù)部分。IEEE 754 規(guī)定,在計算機內(nèi)部保存 M 時默認(rèn)這個數(shù)的第一位總是1,因此可以被舍去,只保存后面的 xxxxxxx 部分。比如保存1.01的時候只保存01,等到讀取的時候再把第一位的1加上去,這樣做的目的就是為了節(jié)省一位有效數(shù)字,以32位浮點數(shù)為例,留給有效數(shù)字M的只有23位,將第一位的1舍去以后等于保存24位有效數(shù)字。

至于指數(shù)E,情況較為復(fù)雜。首先,E為一個無符號指數(shù),這意味著,如果E為8位,它的取值范圍為 0 ~ 255,如果E為11位,它的取值范圍為0 ~ 2047。但是我們知道科學(xué)計數(shù)中的E是是可以出現(xiàn)負(fù)值的,所以IEEE 754規(guī)定,E的真實值必須再減去一個中間數(shù),對于8位的E,這個中間數(shù)是127,對于11位的E,這個中間數(shù)是1023。

比如2102^{10}210的E是10,所以保存成32位浮點數(shù)時,必須保存成 10+127=137,即 10001001。

然后指數(shù)E還可以分成三種情況:

  • E不全為0或不全為1:這時,浮點數(shù)就采用上面的規(guī)則表示,即指數(shù)E的計算值減去127(或1023),得到真實值,再將有效數(shù)字M前加上第一位的1。

  • E全為0:這時,浮點數(shù)的指數(shù)E等于1 ~ 127(或1 ~ 1023),有效數(shù)字M不再加上第一位的1,而是還原成0.xxxxxxx的小數(shù),這樣做是為了表示±0,以及接近0的很小的數(shù)字。

  • E全為1:這時,如果有效數(shù)字M全為0,表示±無窮大(正負(fù)取決于符號位S);如果有效數(shù)字M不全為0,表示這個數(shù)不是一個數(shù)(NaN)。

示例:浮點數(shù) 9.0 如何用二進制表示?還原成十進制又是多少?

首先,浮點數(shù) 9.0 等于二進制的 1001.0,即 1.001?231.001 *2^31.001?23

那么,第一位的符號位 S=0,有效數(shù)字 M 等于 001 后面再加 20 個 0,湊滿 23 位,指數(shù) E 等于 3+127=130,即 10000010。

所以,寫成二進制形式,應(yīng)該是 S+E+M,即0 10000010 001 0000 0000 0000 0000 0000。這個 32 位的二進制數(shù),還原成十進制,正是 1091567616。

注:雖然在 JavaScript 中無論是小數(shù)還是整數(shù)都是按照64位的浮點數(shù)形式存儲,但是進行整數(shù)運算會自動轉(zhuǎn)換為32位的有符號整數(shù),例如位運算,有符號整數(shù)使用31位表示整數(shù)的數(shù)值,用第32位表示整數(shù)的符號,數(shù)值范圍是?231-2^{31}?231 ~ 2312^{31}231。

二、浮點數(shù)運算的精度丟失

問題緣由

眾所周知在 JavaScript 中 0.1+0.2 不等于 0.3,實際上所有浮點數(shù)值存儲遵循 IEEE 754 標(biāo)準(zhǔn)的編程語言中都會存在這個問題,這是因為計算機中小數(shù)的存儲先是轉(zhuǎn)換成二進制進行存儲的,而 0.1、0.2 轉(zhuǎn)換成二進制分別為:

(0.1)10 => (00011001100110011001(1001)...)2 (0.2)10 => (00110011001100110011(0011)...)2

可以發(fā)現(xiàn),0.1 和 0.2 轉(zhuǎn)成二進制之后都是一個無限循環(huán)的數(shù),前面提到尾數(shù)位只能存儲最多 53 位有效數(shù)字,這時候就必須來進行四舍五入了,而這個取舍的規(guī)則就是在 IEEE 754 中定義的,0.1 最終能被存儲的有效數(shù)字是

0001(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)101 + (0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)01 = 0100(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)111

最終的這個二進制數(shù)轉(zhuǎn)換成十進制的就是 0.30000000000000004 ,這兒需要注意,53 位的存儲位指的是能存 53 位有效數(shù)字,因此前置的 0 不算,要往后再取到  53 位有效數(shù)字為止。

因此,精度丟失的問題實際上用一句話概括就是計算機中用二進制存儲小數(shù),而大部分小數(shù)轉(zhuǎn)成二進制后都是無限循環(huán)的值,因此存在取舍問題,也就是精度丟失。

解決辦法

ES6 在 Number 對象上新增了一個極小常量:Number.EPSILON,值為 2.220446049250313e-16,引入這么一個常量就是為了為浮點數(shù)計算設(shè)置一個誤差范圍,如果這個誤差小于 Number.EPSILON 我們就認(rèn)為得到了準(zhǔn)確結(jié)果。

三、大整數(shù)的運算精度丟失及溢出

問題緣由

在介紹問題的具體緣由之前我想先給大家介紹一下所謂最大安全整數(shù)范圍以及最大數(shù)字絕對值的范圍是如何得到的?

JavaScript 能夠表示的數(shù)字的絕對值范圍是 5e-324 ~ 1.7976931348623157e+308,這兩個取值可以通過 Number.MIN_VALUE 和 Number.MAX_VALUE 這兩個字段來表示,如果某次計算的結(jié)果得到了一個超出 JavaScript 數(shù)值范圍的,那么這個數(shù)值會自動被轉(zhuǎn)換為特殊的 Infinity 值,具體來說,如果這個數(shù)是負(fù)數(shù),則會被轉(zhuǎn)換成 -Infinity(負(fù)無窮),如果這個數(shù)值是正數(shù),則會被轉(zhuǎn)換成 Infinity(正無窮)。

示例:

console.log(Number.MAX_VALUE) // 1.7976931348623157e+308
console.log(Number.MIN_VALUE) // 5e-324
console.log(Number.MAX_VALUE + Number.MAX_VALUE) // Infinity

那么這個取值范圍是如何得到的呢?

前面說到 JavaScript 中數(shù)值的保存采用的是雙精度浮點型,遵循 IEEE 754 標(biāo)準(zhǔn),在 ECMAScript 規(guī)范中規(guī)定指數(shù) E 的范圍在 -1074 ~ 971,雙精度浮點型中有效數(shù)字 M 的存儲位為52,但是有效數(shù)字 M 由于可以省略第一位1,節(jié)省一個存儲位,因此有效數(shù)字M可以存儲的范圍為 1 ~ 2532^{53}253,因此 JavaScript 中 Number 能表示的最大數(shù)字絕對值范圍是 2?10742^{-1074}2?1074 ~ 253+9712^{53+971}253+971。

注:通過 Number.isFinite()(ES6引入)和 isFinite() 方法可以判斷一個數(shù)值是不是有窮的,即如果參數(shù)位于最小與最大數(shù)值之間時會返回 true。

讓我們回歸主題,為什么會出現(xiàn)大整數(shù)的運算精度丟失及溢出呢?

JavaScript 中最大安全整數(shù)的范圍是 ?253-2^{53}?253 ~ 2532^{53}253,不包括兩個端點,即 -9007199254740991 ~ 9007199254740991,可以通過 Number.MIN_SAFE_INTEGER 和 Number.MAX_SAFE_INTEGER 字段查詢,超出這個范圍的整數(shù)計算都是不準(zhǔn)確的,例如:

console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991
console.log(9007199254740991 + 2) // 9007199254740992

最大安全整數(shù)9007199254740991對應(yīng)的二進制數(shù)如圖:

JavaScript中Number類型常見誤區(qū)如何解決

53位有效數(shù)字都存儲滿了之后,想要表示更大的數(shù)字,就只能往指數(shù)數(shù)加一位,這時候尾數(shù)因為沒有多余的存儲空間,因此只能補0。

JavaScript中Number類型常見誤區(qū)如何解決

如圖所示,在指數(shù)位為53的情況下,最后一位尾數(shù)位為0的數(shù)字可以被精確表示,而最后一位尾數(shù)位為1的數(shù)字都不能被精確表示。也就是可以被精確表示和不能被精確表示的比例是1:1。

同理,當(dāng)指數(shù)為54的時候,只有最后兩位尾數(shù)為00的可以被精確表示,也就是可以被精確表示和不能被精確表示的比例是1:3,當(dāng)有效位數(shù)達到 x(x>53) 的時候,可以被精確表示和不能被精確表示的比例將是1 : 2^(x-53)^ - 1。

可以預(yù)見的是,在指數(shù)越來越高的時候,這個指數(shù)會成指數(shù)增長,因此在 Number.MAX_SAFE_INTEGER ~ Number.MAX_VALUE 之間可以被精確表示的整數(shù)可以說是鳳毛麟角。

之所以會有最大安全整數(shù)這個概念,本質(zhì)上還是因為數(shù)字類型在計算機中的存儲結(jié)構(gòu)。在尾數(shù)位不夠補零之后,只要是多余的尾數(shù)為1所對應(yīng)的整數(shù)都不能被精確表示。

可以發(fā)現(xiàn),不管是浮點數(shù)計算的計算結(jié)果錯誤和大整數(shù)的計算結(jié)果錯誤,最終都可以歸結(jié)到JS的精度只有53位(尾數(shù)只能存儲53位的有效數(shù)字)。

解決辦法

那么我們在日常工作中碰到這兩個問題該如何解決呢?

大而全的解決方案就是使用 mathjs,看一下 mathjs 的輸出:

math.config({
    number: 'BigNumber',      
    precision: 64 
});
console.log(math.format(math.eval('0.1 + 0.2'))); // '0.3'
console.log(math.format(math.eval('0.23 * 0.34 * 0.92'))); // '0.071944'
console.log(math.format(math.eval('9007199254740991 + 2'))); 
// '9.007199254740993e+15'

以上就是關(guān)于“JavaScript中Number類型常見誤區(qū)如何解決”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI