<p>
Number 類型應該是 ECMAScript 中最令人關注的數據類型了,這種類型使用 IEEE754 格式來表示整數和浮點數值(浮點數值在某些語言中也被稱為雙精度數值)。為支持各種數值類型, ECMA-262 定義了不同的數值字面量格式。
</p>
<p>
最基本的數值字面量格式是十進制整數,十進制整數可以像下面這樣直接在代碼中輸入:
</p>
<pre>var intNum = 55; // 整數</pre>
<p>
除了以十進制表示外,整數還可以通過八進制(以 8 為基數)或十六進制(以 16 為基數)的字面值來表示。其中,八進制字面值的第一位必須是零(0) ,然后是八進制數字序列(0~7) 。如果字面值中的數值超出了范圍,那么前導零將被忽略,后面的數值將被當作十進制數值解析。請看下面的例子:
</p>
<pre>var octalNum1 = 070; // 八進制的 56
var octalNum2 = 079; // 無效的八進制數值——解析為 79
var octalNum3 = 08; // 無效的八進制數值——解析為 8</pre>
<p>
八進制字面量在嚴格模式下是無效的,會導致支持的 JavaScript 引擎拋出錯誤。十六進制字面值的前兩位必須是 0x,后跟任何十六進制數字(0~9 及 A~F)。其中,字母 A~F可以大寫,也可以小寫。如下面的例子所示:
</p>
<pre>var hexNum2 = 0x1f; // 十六進制的 31</pre>
<p>
在進行算術計算時,所有以八進制和十六進制表示的數值最終都將被轉換成十進制數值。
</p>
<pre>var hexNum1 = 0xA; // 十六進制的 10
var hexNum2 = 0x1f; // 十六進制的 31</pre>
<blockquote>
① n/a(或 N/A),是 not applicable 的縮寫,意思是“不適用”。
</blockquote>
<p>
鑒于 JavaScript 中保存數值的方式,可以保存正零(<code>+0</code>)和負零(<code>-0</code>)。正零和負零被認為相等,但為了讀者更好地理解上下文,這里特別做此說明。
</p>
<h2>
1. 浮點數值
</h2>
<p>
所謂浮點數值,就是該數值中必須包含一個小數點,并且小數點后面必須至少有一位數字。雖然小數點前面可以沒有整數,但我們不推薦這種寫法。以下是浮點數值的幾個例子:
</p>
<pre>var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1; // 有效,但不推薦</pre>
<p>
由于保存浮點數值需要的內存空間是保存整數值的兩倍,因此 ECMAScript 會不失時機地將浮點數值轉換為整數值。顯然,如果小數點后面沒有跟任何數字,那么這個數值就可以作為整數值來保存。同樣地,如果浮點數值本身表示的就是一個整數(如 1.0) ,那么該值也會被轉換為整數,如下面的例子所示:
</p>
<pre>var floatNum1 = 1.; // 小數點后面沒有數字——解析為 1
var floatNum2 = 10.0; // 整數——解析為 10</pre>
<p>
對于那些極大或極小的數值,可以用 e 表示法(即科學計數法)表示的浮點數值表示。用 e 表示法表示的數值等于 e 前面的數值乘以 10 的指數次冪。 ECMAScript 中 e 表示法的格式也是如此,即前面是一個數值(可以是整數也可以是浮點數),中間是一個大寫或小寫的字母 E,后面是 10 的冪中的指數,該冪值將用來與前面的數相乘。下面是一個使用 e 表示法表示數值的例子:
</p>
<pre>var floatNum = 3.125e7; // 等于 31250000</pre>
<p>
在這個例子中,使用 e 表示法表示的變量 floatNum 的形式雖然簡潔,但它的實際值則是 31250000。在此, e 表示法的實際含義就是“ 3.125 乘以 107”。
</p>
<p>
也可以使用 e 表示法表示極小的數值,如 0.00000000000000003,這個數值可以使用更簡潔的 3e?17表示。在默認情況下, ECMASctipt 會將那些小數點后面帶有 6 個零以上的浮點數值轉換為以 e 表示法表示的數值(例如, 0.0000003 會被轉換成 3e?7)。
</p>
<p>
浮點數值的最高精度是 17 位小數,但在進行算術計算時其精確度遠遠不如整數。例如, 0.1 加 0.2的結果不是 0.3,而是 0.30000000000000004。這個小小的舍入誤差會導致無法測試特定的浮點數值。例如:
</p>
<pre>if (a + b == 0.3){ // 不要做這樣的測試!
alert("You got 0.3.");
}</pre>
<p>
在這個例子中,我們測試的是兩個數的和是不是等于 0.3。如果這兩個數是 0.05 和 0.25, 或者是 0.15和 0.15 都不會有問題。而如前所述,如果這兩個數是 0.1 和 0.2,那么測試將無法通過。因此,永遠不要測試某個特定的浮點數值。
</p>
<p>
</p>
<blockquote>
關于浮點數值計算會產生舍入誤差的問題,有一點需要明確:這是使用基于IEEE754 數值的浮點計算的通病, ECMAScript 并非獨此一家;其他使用相同數值格式的語言也存在這個問題。
</blockquote>
<p>
</p>
<h2>
2. 數值范圍
</h2>
<p>
由于內存的限制, ECMAScript 并不能保存世界上所有的數值。 ECMAScript 能夠表示的最小數值保存在 Number.MIN_VALUE 中——在大多數瀏覽器中,這個值是 5e-324;能夠表示的最大數值保存在Number.MAX_VALUE 中——在大多數瀏覽器中,這個值是 1.7976931348623157e+308。如果某次計算的結果得到了一個超出 JavaScript 數值范圍的值,那么這個數值將被自動轉換成特殊的 Infinity 值。具體來說,如果這個數值是負數,則會被轉換成-Infinity(負無窮),如果這個數值是正數,則會被轉換成 Infinity(正無窮)。
</p>
<p>
如上所述,如果某次計算返回了正或負的 Infinity 值,那么該值將無法繼續參與下一次的計算,因為 Infinity 不是能夠參與計算的數值。要想確定一個數值是不是有窮的(換句話說,是不是位于最小和最大的數值之間),可以使用 isFinite()函數。這個函數在參數位于最小與最大數值之間時會返回 true,如下面的例子所示:
</p>
<pre>var result = Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(result)); //false</pre>
<p>
<a href="http://www.shouce.ren/study/api/s/8358" target="_blank" class="btn btn-sm btn-success">運行一下</a>
</p>
<p>
盡管在計算中很少出現某些值超出表示范圍的情況,但在執行極小或極大數值的計算時,檢測監控這些值是可能的,也是必需的。
</p>
<p>
</p>
<blockquote>
訪問 Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以得到負和正 Infinity 的值。可以想見,這兩個屬性中分別保存著-Infinity 和Infinity。
</blockquote>
<p>
</p>
<h2>
3. NaN
</h2>
<p>
NaN,即非數值(Not a Number)是一個特殊的數值,這個數值用于表示一個本來要返回數值的操作數未返回數值的情況(這樣就不會拋出錯誤了)。例如,在其他編程語言中,任何數值除以 0 都會導致錯誤,從而停止代碼執行。但在 ECMAScript 中,任何數值除以 0 會返回 NaN①,因此不會影響其他代碼的執行。
</p>
<p>
NaN 本身有兩個非同尋常的特點。首先,任何涉及 NaN 的操作(例如 NaN/10)都會返回 NaN,這個特點在多步計算中有可能導致問題。其次, NaN 與任何值都不相等,包括 NaN 本身。例如,下面的代碼會返回 false:
</p>
<pre>alert(NaN == NaN); //false</pre>
<p>
針對 NaN 的這兩個特點, ECMAScript 定義了 isNaN()函數。這個函數接受一個參數,該參數可以是任何類型,而函數會幫我們確定這個參數是否“不是數值”。 isNaN()在接收到一個值之后,會嘗試將這個值轉換為數值。某些不是數值的值會直接轉換為數值,例如字符串"10"或 Boolean 值。而任何不能被轉換為數值的值都會導致這個函數返回 true。請看下面的例子:
</p>
<pre>alert(isNaN(NaN)); //true
alert(isNaN(10)); //false( 10 是一個數值)
alert(isNaN("10")); //false(可以被轉換成數值 10)
alert(isNaN("blue")); //true(不能轉換成數值)
alert(isNaN(true)); //false(可以被轉換成數值 1)
</pre>
<a href="http://www.shouce.ren/study/api/s/8359" target="_blank" class="btn btn-sm btn-success">運行一下</a><br />
<p>
</p>
<blockquote>
① 原書如此,但實際上只有 0 除以 0 才會返回 NaN,正數除以 0 返回 Infinity,負數除以 0 返回-Infinity。
</blockquote>
<p>
</p>
<p>
這個例子測試了 5 個不同的值。測試的第一個值是 NaN 本身,結果當然會返回 true。然后分別測試了數值 10 和字符串"10",結果這兩個測試都返回了 false,因為前者本身就是數值,而后者可以被轉換成數值。但是,字符串"blue"不能被轉換成數值,因此函數返回了 true。由于 Boolean 值 true可以轉換成數值 1,因此函數返回 false。
</p>
<p>
</p>
<blockquote>
盡管有點兒不可思議,但 isNaN()確實也適用于對象。在基于對象調用 isNaN()函數時,會首先調用對象的 valueOf()方法,然后確定該方法返回的值是否可以轉換為數值。如果不能,則基于這個返回值再調用 toString()方法,再測試返回值。而這個過程也是 ECMAScript 中內置函數和操作符的一般執行流程,更詳細的內容請參見 3.5 節。
</blockquote>
<p>
</p>
<h2>
4. 數值轉換
</h2>
<p>
有 3 個函數可以把非數值轉換為數值: Number()、 parseInt()和 parseFloat()。第一個函數,即轉型函數 Number()可以用于任何數據類型,而另兩個函數則專門用于把字符串轉換成數值。這 3 個函數對于同樣的輸入會有返回不同的結果。
</p>
<p>
Number()函數的轉換規則如下。
</p>
<ul>
<li>
如果是數字值,只是簡單的傳入和返回。
</li>
<li>
如果是 Boolean 值, true 和 false 將分別被轉換為 1 和 0。<br />
</li>
<li>
如果是 null 值,返回 0。
</li>
<li>
如果是 undefined,返回 NaN。
</li>
<li>
如果是字符串,遵循下列規則:
</li>
<li>
如果字符串中只包含數字(包括前面帶正號或負號的情況),則將其轉換為十進制數值,即"1"會變成 1, "123"會變成 123,而"011"會變成 11(注意:前導的零被忽略了);
</li>
<li>
如果字符串中包含有效的浮點格式,如"1.1",則將其轉換為對應的浮點數值(同樣,也會忽略前導零);
</li>
<li>
如果字符串中包含有效的十六進制格式,例如"0xf",則將其轉換為相同大小的十進制整數值;
</li>
<li>
如果字符串是空的(不包含任何字符),則將其轉換為 0;
</li>
<li>
如果字符串中包含除上述格式之外的字符,則將其轉換為 NaN。
</li>
<li>
如果是對象,則調用對象的 valueOf()方法,然后依照前面的規則轉換返回的值。如果轉換的結果是 NaN,則調用對象的 toString()方法,然后再次依照前面的規則轉換返回的字符串值。
</li>
</ul>
<p>
根據這么多的規則使用 Number()把各種數據類型轉換為數值確實有點復雜。下面還是給出幾個具體的例子吧。
</p>
<pre>var num1 = Number("Hello world!"); //NaN
var num2 = Number(""); //0
var num3 = Number("000011"); //11
var num4 = Number(true); //1</pre>
<a href="http://www.shouce.ren/study/api/s/8360" target="_blank" class="btn btn-sm btn-success">運行一下</a><br />
<p>
首先,字符串"Hello world!"會被轉換為 NaN,因為其中不包含任何有意義的數字值。空字符串會被轉換為 0。字符串"000011"會被轉換為 11,因為忽略了其前導的零。最后, true 值被轉換為 1。
</p>
<p>
</p>
<blockquote>
一元加操作符(3.5.1 節將介紹)的操作與 Number()函數相同。
</blockquote>
<p>
</p>
<p>
由于 Number()函數在轉換字符串時比較復雜而且不夠合理,因此在處理整數的時候更常用的是parseInt()函數。 parseInt()函數在轉換字符串時,更多的是看其是否符合數值模式。它會忽略字符串前面的空格,直至找到第一個非空格字符。如果第一個字符不是數字字符或者負號, parseInt()就會返回 NaN;也就是說,用 parseInt()轉換空字符串會返回 NaN(Number()對空字符返回 0) 。如果第一個字符是數字字符, parseInt()會繼續解析第二個字符,直到解析完所有后續字符或者遇到了一個非數字字符。例如, "1234blue"會被轉換為 1234,因為"blue"會被完全忽略。類似地, "22.5"會被轉換為 22,因為小數點并不是有效的數字字符。
</p>
<p>
如果字符串中的第一個字符是數字字符, parseInt()也能夠識別出各種整數格式(即前面討論的十進制、八進制和十六進制數)。也就是說,如果字符串以"0x"開頭且后跟數字字符,就會將其當作一個十六進制整數;如果字符串以"0"開頭且后跟數字字符,則會將其當作一個八進制數來解析。
</p>
<p>
為了更好地理解 parseInt()函數的轉換規則,下面給出一些例子:
</p>
<pre>var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xA"); // 10(十六進制數)
var num4 = parseInt(22.5); // 22
var num5 = parseInt("070"); // 56(八進制數)
var num6 = parseInt("70"); // 70(十進制數)
var num7 = parseInt("0xf"); // 15(十六進制數)</pre>
<a href="http://www.shouce.ren/study/api/s/8361" target="_blank" class="btn btn-sm btn-success">運行一下</a><br />
<p>
在使用 parseInt()解析像八進制字面量的字符串時, ECMAScript 3 和 5 存在分歧。例如:
</p>
<pre>//ECMAScript 3 認為是 56(八進制), ECMAScript 5 認為是 70(十進制)
var num = parseInt("070");</pre>
<p>
在 ECMAScript 3 JavaScript 引擎中, "070"被當成八進制字面量,因此轉換后的值是十進制的 56。而在 ECMAScript 5 JavaScript 引擎中, parseInt()已經不具有解析八進制值的能力,因此前導的零會被認為無效,從而將這個值當成"70",結果就得到十進制的 70。在 ECMAScript 5 中,即使是在非嚴格模式下也會如此。
</p>
<p>
為了消除在使用 parseInt()函數時可能導致的上述困惑,可以為這個函數提供第二個參數:轉換時使用的基數(即多少進制)。如果知道要解析的值是十六進制格式的字符串,那么指定基數 16 作為第二個參數,可以保證得到正確的結果,例如:
</p>
<pre>var num = parseInt("0xAF", 16); //175</pre>
<p>
實際上,如果指定了 16 作為第二個參數,字符串可以不帶前面的"0x",如下所示:
</p>
<pre>var num1 = parseInt("AF", 16); //175
var num2 = parseInt("AF"); //NaN</pre>
<a href="http://www.shouce.ren/study/api/s/8362" target="_blank" class="btn btn-sm btn-success">運行一下</a><br />
<p>
這個例子中的第一個轉換成功了,而第二個則失敗了。差別在于第一個轉換傳入了基數,明確告訴parseInt()要解析一個十六進制格式的字符串;而第二個轉換發現第一個字符不是數字字符,因此就自動終止了。
</p>
<p>
指定基數會影響到轉換的輸出結果。例如:
</p>
<pre>var num1 = parseInt("10", 2); //2 (按二進制解析)
var num2 = parseInt("10", 8); //8 (按八進制解析)
var num3 = parseInt("10", 10); //10 (按十進制解析)
var num4 = parseInt("10", 16); //16 (按十六進制解析)</pre>
<a href="http://www.shouce.ren/study/api/s/8363" target="_blank" class="btn btn-sm btn-success">運行一下</a><br />
<p>
不指定基數意味著讓 parseInt()決定如何解析輸入的字符串,因此為了避免錯誤的解析,我們建議無論在什么情況下都明確指定基數。
</p>
<p>
</p>
<blockquote>
多數情況下,我們要解析的都是十進制數值,因此始終將 10 作為第二個參數是非常必要的。
</blockquote>
<p>
</p>
<p>
與 parseInt()函數類似, parseFloat()也是從第一個字符(位置 0)開始解析每個字符。而且也是一直解析到字符串末尾,或者解析到遇見一個無效的浮點數字字符為止。也就是說,字符串中的第一個小數點是有效的,而第二個小數點就是無效的了,因此它后面的字符串將被忽略。舉例來說,
</p>
<p>
</p>
<pre>"22.34.5"將會被轉換為 22.34。</pre>
<p>
</p>
<p>
除了第一個小數點有效之外, parseFloat()與 parseInt()的第二個區別在于它始終都會忽略前導的零。 parseFloat()可以識別前面討論過的所有浮點數值格式,也包括十進制整數格式。但十六進制格式的字符串則始終會被轉換成 0。由于 parseFloat()只解析十進制值,因此它沒有用第二個參數指定基數的用法。最后還要注意一點:如果字符串包含的是一個可解析為整數的數(沒有小數點,或者小數點后都是零) , parseFloat()會返回整數。以下是使用 parseFloat()轉換數值的幾個典型示例。
</p>
<pre>var num1 = parseFloat("1234blue"); //1234 (整數)
var num2 = parseFloat("0xA"); //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000</pre>
<a href="http://www.shouce.ren/study/api/s/8364" target="_blank" class="btn btn-sm btn-success">運行一下</a>