溫馨提示×

          JavaScript中Number類型常見誤區如何解決

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

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

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

          3.0 === 3 // true

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

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

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

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

          一、存儲方式

          JavaScript 中的 Number 類型使用的是雙精度浮點型,即其他語言中的 double 類型,雙精度浮點數使用 8 個字節即 64bit 來進行存儲,現代計算機中浮點數大多是以國際標準 IEEE 754 來存儲,存儲過程分兩步,

          • 把浮點數轉換為對應的二進制數,并用科學計數法表示

          • 將轉換之后的數通過 IEEE 754 標準表示成真正會在計算機存儲的值。

          根據 IEEE 754 標準任何一個二進制浮點數 V 都可以表示成:

          JavaScript中Number類型常見誤區如何解決

          JavaScript中Number類型常見誤區如何解決

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

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

          JavaScript中Number類型常見誤區如何解決

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

          JavaScript中Number類型常見誤區如何解決

          注意:IEEE754 對于有效數字M和指數E還有一些特別的規定。

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

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

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

          然后指數E還可以分成三種情況:

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

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

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

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

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

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

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

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

          二、浮點數運算的精度丟失

          問題緣由

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

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

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

          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

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

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

          解決辦法

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

          三、大整數的運算精度丟失及溢出

          問題緣由

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

          JavaScript 能夠表示的數字的絕對值范圍是 5e-324 ~ 1.7976931348623157e+308,這兩個取值可以通過 Number.MIN_VALUE 和 Number.MAX_VALUE 這兩個字段來表示,如果某次計算的結果得到了一個超出 JavaScript 數值范圍的,那么這個數值會自動被轉換為特殊的 Infinity 值,具體來說,如果這個數是負數,則會被轉換成 -Infinity(負無窮),如果這個數值是正數,則會被轉換成 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 中數值的保存采用的是雙精度浮點型,遵循 IEEE 754 標準,在 ECMAScript 規范中規定指數 E 的范圍在 -1074 ~ 971,雙精度浮點型中有效數字 M 的存儲位為52,但是有效數字 M 由于可以省略第一位1,節省一個存儲位,因此有效數字M可以存儲的范圍為 1 ~ 2532^{53}253,因此 JavaScript 中 Number 能表示的最大數字絕對值范圍是 2?10742^{-1074}2?1074 ~ 253+9712^{53+971}253+971。

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

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

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

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

          最大安全整數9007199254740991對應的二進制數如圖:

          JavaScript中Number類型常見誤區如何解決

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

          JavaScript中Number類型常見誤區如何解決

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

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

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

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

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

          解決辦法

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

          大而全的解決方案就是使用 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'

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

          推薦閱讀:Python學習之常見的Number數據類型

          免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

          主題地圖

          超级A片免费看