JavaSrcipt的數字(number):深入理解內部機制
一、數字的語法
JavaScript中的數字字面量一般用十進位制表示。在JavaScript中表示數字的資料型別只有一種Number,這種天使與魔鬼同體的資料型別也就只有js了。
//同時表達整數和浮點數 var a = 78, b = 78.3; console.log(typeof a);//number console.log(typeof b);//number
然後還有一些奇葩的數字表示法:
var a = 0.42, b = .42; if(a === b){ console.log("你講得對。") }
一般情況下,奇葩都是成雙成對的:
vara = 69.0, b = 69.; if(a === b){ console.log("你講得對。") }
這種奇葩你認識它就好了,最好不要出現在你的程式碼裡,記得這種長得醜的寫法不報錯,但也沒事別給自己挖坑。
var a = 89.900, b = 98.0; console.log(a);//89.9 console.log(b);//98
小數點後面最後的面的零不管幾個都省略,有時候發點神經,不拖泥帶水還是挺瀟灑的。然後還有指數計數格式計數與指數顯示。
var a = 9E10; console.log(a);//90000000000--通常我們使用指數計數法但是不會按照指數計數法顯示console.log(a.toExponential());//9e+10--通過toExponential方法轉換成指數計數法字串形式顯示 var b = a * a; console.log(b);//8.1e+21--當數字長度超出一定範圍就會自動轉成指數計數法 var c = 1 / b; console.log(c);//1.234567901234568e-22--小寫範圍超出也會被轉成指數計數法
這時候就有一個尷尬的問題來了,顯示的是指數計數字面量,有多少人認識呢?所以就需要做數字化字面量處理:(下面的解決方案只能解決JavaScript整數安全範圍內和最小小數範圍內的數值。超出這個範圍的數值聽說可以解除Math.js,我也是在別人的部落格裡看到有介紹過的,沒有親測過。)
function toNonExponential(num) { var m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/); //通過match檢索每個整個分組的值(非抓捕除外),並以陣列表示 //(m[1] || '').length;//--\d*--獲取到小數位的長度 //m[2];//--([+-]\d+)--獲取到指數 //(m[1] || '').length - m[2];小數位長度與指數的查(小數的長度) //小數位長度為負數時,設定為0--math.max取最大值 return num.toFixed(Math.max(0, (m[1] || '').length - m[2])); } toNonExponential(3.3e-7) // "0.00000033" toNonExponential(3e-7) // "0.0000003" toNonExponential(1.401e10) // "14010000000" toNonExponential(0.0004) // "0.0004"View Code
指定小數部分的顯示數位:Number.toFixed(x)
var a = 878.59; a.toFixed(0);//"879" a.toFixed(1);//"878.6" a.toFixed(2);//"878.88" a.toFixed(3);//"878.880"
關於toFixed方法會對數值做四捨五入處理,小數位不夠時後面自動用0補齊,返回的值是字串型別,引數x的取值範圍是0~20。接下來看看有效數位處理方法toPrecision(x)
var a = 34.29; a.toPrecision(1);//"3e+1" a.toPrecision(2);//"34" a.toPrecision(3);//"34.3" a.toPrecision(4);//"34.29" a.toPrecision(5);//"34.290" //注意報錯 34.toPrecision(3)//SyntaxError 34.toFixed(2)//SytaxError (34).toPrecision(3);//34.0 (34).toFixed(2);//34.00
Number.toPrecision(x)方法也是會進行四捨五入操作,並且在數位無法表達數值的時候回進行科學計數法處理。值得注意的是toFixed(x)和toPrecision(x)這兩個方法在遇到整數字面量呼叫時,需要用小括號將數值包裹起來,不然會報錯。
最後就是一些特殊格式的數字字面量:
console.log(0xf3);//243--數字字面量十六進位制 console.log(0363);//243--數字字面量八進位制 //ES6支援的新格式 console.log(0o363);//243--ES6版本的數字字面量八進位制 console.log(0b11110011);//234--ES6版本數字字面量二進位制
二、很小的數字與JavaScript的數值精確問題
console.log(0.1 + 0.2 === 0.3);//false
從上面的程式碼我們可以看到一個違背了數學邏輯的問題,這是為什麼呢?因為JavaScript所遵循IEEE754程式設計語言規範,這不只是JS的問題,而是所有遵循這一規範的程式語言都如此,也是通常說的二進位制浮點數的精度缺陷,在這一精度下0.1+0.2的結果是0.30000000000000004,所以條件判斷結果為false。
解決這一問題的辦法就是設定一個誤差範圍,也就是通常說的“機器精度”,這個值通常是2^-52(2.220446049250313e-16)。在ES6中該值定義在Number.EPSILON中,在ES6的環境下我們可以直接拿來用,來版本環境可以通過計算獲得這個值來使用:
if(!Number.EPSILON){ Number.EPSILON = Math.pow(2,-52); } //這時候我們就可以來解決兩個數字是否相等的問題了 function numbersCLoseEE(n1,n2){ return Math.abs(n1 - n2) < Number.EPSILON; } var a = 0.1 + 0.2; var b = 0.3; numbersCLoseEE(a,b);
三、整數(範圍、檢測、32位有符號整數)
3.1JavaScript中整數最大可呈現2^53 - 1,即9007199254740991,在ES6中被定義為Number.MAX_SAFE_INTEGER。整數最小可呈現-(2^53 - 1),即-9007199254740991,在ES6中定義為Number.MIN_SAFE_INTEGER。超出這個範圍的數字就只能採用字串的方式來呈現了。
3.2關於檢測一個數值是否是整數,可以使用ES6中的NumBer.isInteger(..)方法,老版本就只能自己做相容處理:
Number.isInteger(42); //true Number.isInteger(42.00); //true Number.isInteger(42.3); //false //Number.isInteger(..)的相容模式 if(!Number.isInteger){ Number.isInteger = function(num){ return typeof num == "number" && num % 1 == 0; } }
關於檢測一個數值是否是安全整數(即在最大範圍和最小範圍內的整數),可以使用ES6中的Number.isSafeInteger(...)方法,老版本做相容處理:
if(!Number.isSafeInteger){ Number.isSafeInteger = function(num){ return Number.isInteger(num) && Math.abs(num) <= Number.MAX_SAFE_INTEGER; } }
ES 6 增加了以下三個 Number 物件的屬性:
- EPSILON: 表示 1 和比最接近 1 且大於 1 的最小 Number 之間的差別
- MIN_SAFE_INTEGER: 表示在 JavaScript中最小的安全的 integer 型數字 (
-(253 - 1)
)。 - MAX_SAFE_INTEGER: 表示在 JavaScript 中最大的安全整數(
253 - 1
)。
3.3JavaScript32位有符號整數:
雖然最大整數能夠達到53位,但是有些數字操作只適應於32位數字,所以這些操作的數字範圍只能在Math.pow(-2,31)到Math.pow(2,31)。
或運算子(“|”)只適應於32位以內的整數運算,所以有任意大小整數通過或運算子運算的話沒有意義,比如任意大小整數a與0的運算,(a | 0)從運算範圍來理解,這個運算本質上不存在任何意義。
四、特殊數字
1.不是數字的數字(NaN)
數學運算時,運算元不是數字型別或者無法解析為常規的十進位制或十六進位制數字,就無法返回有效的數字,這種情況就會返回NaN。
var a = 2 / "foo"; //NaN typeof a === "number"; //true
作為一個不是數字的Number型別的值,有一個特性是它不等於任何值,包括他自己(這傢伙傻的可愛)。
var a = 2 / "foo"; a == NaN; //false a === NaN; //false //而且它很確定自己就不是自己 NaN != NaN; // true
既然無法進行比較,那如果在程式中我們由不得不比較它呢?全域性物件window上有一個方法isNaN(...)可以用來判斷它!
var a = 2 / "fuh"; window.isNaN(a);//true
但是,全域性物件上的isNaN(...)並不靠譜,這個方法在檢測值的時候,只要被判斷的值不是數字就返回true。
var a = "aaa"; console.log(window.isNaN(a));//true
然後ES6在Number物件上添加了isNaN的方法解決了這個問題,但是老版本的瀏覽器就得要做相容處理了:
if(!Number.isNaN){ Number.isNaN = function(n){ return ( typeof n === "number" && window.isNaN(n) ); }; } var a = 2 / "abc"; var b = "abc"; console.log(Number.isNaN(a));//true console.log(Number.isNaN(b));//false
這個相容的寫法有點累贅,其實還有一個更簡單的寫法:
if(!Number.isNaN){ Number.isNaN = function(n){ return n !== n; } }
2.無窮數
在JavaScript中除數是可以為0的,當1除以0時結果為Infinity(即Number.POSITIVE_INFINITY)。無窮數在JavaScript中也存在正無窮和負無窮,當一個負數除以0時,就可以得到-Infinity。
var a = 1 / 0;//Infinity var a = -1 / 0;//-Infinity
因為JavaScript遵循IEEE754規範,在斷定斷定無窮數時存在向上和向下取值行為,我們知道在JavaScript中定義了最大值屬性(Number.MAX_VALUE);但這個最大值並不代表再加上一個數就是無窮了,而且這個數跟一的差距還特別大,這種界定無窮的方法通俗來講就是這個介於最大值和無窮之間的數更接近那一邊。
console.log(Number.MAX_VALUE);//1.7976931348623157e+308 var a = Number.MAX_VALUE; a + a; // Infinity a + Math.pow(2,970);// Infinity a + Math.pow(2,969);// 1.7976931348623157e+308 //這部分不太必要過多研究,可能在你的工作中永遠不會用到這個知識點,有興趣的話作為學術討論就好
要注意一點的是無窮除以無窮的結果為NaN。
3.零值
在JavaSrcipt中零是存在正負值的特殊數值,當零除以負數時得到的就是一個(-0),當然一個負數乘以零也是得到(-0);
var a = 0 / -5;//-0 var b = 0 * -6;//-0
在控制檯列印時,這個結果並不一定準確,會因不同的瀏覽器和版本有不同的情況;還有一個情況就是在零值轉換為字串型別是就不會保留負值這個特性了,但是當帶有負號的數字0的字串轉換成數字格式時又會保留負值。
var a = 0 / -9; //-0 a.toString(); //"0" a + ""; //"0" String(a); //"0" + "-0"; //-0 Number("-0"); //-0 JSON.parse("-0"); //-0
既然零值存在正負特性,但是在普通的邏輯運算看來0和-0是同一個東西,這兩個東西是相等的。那麼就會需要有判斷正負的機制,寫一個判斷值為負零(-0)的方法:
function isNegZero(n){ n = Number(n); return (n === 0) && (1 / n === -Infinity); }