1. 程式人生 > 實用技巧 >JavaScript資料型別之Number型別

JavaScript資料型別之Number型別

  Number型別應該是ECMAScript中最令人關注的資料型別了,這種型別使用IEEE754格式來表示整數和浮點數值(浮點數值在某些語言中也被稱為雙精度數值)。為支援各種數值型別,ECMAScript-262定以了不同的數值字面量格式。

  最基本的數值字面量格式是十進位制整數,十進位制整數可以像下面這樣直接在程式碼中輸入:

var intNum = 55;        //整數

  除了以十進位制表示外,整數還可以通過八進位制(以8為基數)或十六進位制(以16為基數)的字面值來表示。其中,八進位制字面值的第一位必須是零(0),然後是八進位制數字序列(0-7).如果字面值中的數值超出了範圍,那麼前導零將被忽略,後面的數值將被當作十進位制數值解析。請看下面的例子:

var octalNum1 = 070;        // 八進位制的56
var octalNum2 = 079;        // 無效的八進位制數值——解析為79
var octalNum3 = 08;          // 無效的八進位制數值——解析為8

  八進位制字面量在嚴格模式下是無效的,會導致該模式的JavaScript引擎丟擲錯誤。

  十六進位制字面值的前兩位必須是0x,後跟任何十六進位制數字(0~9 及 A~F)。其中,字母A~F可以大寫,也可以小寫。如下面的例子所示:

var hexNum1 = 0xA;        // 十六進位制的10
var hexNum2 = 0x1f;        //
十六進位制的31

  在進行算術計算時,所有以八進位制和十六進位制表示的數值最終都將被轉換成十進位制數值。

  鑑於JavaScript 中儲存數值的方式,可以儲存正零(+0)和負零(-0)。正零和負零被認為相等,但為了大家更好的理解上下文,這裡特別做此說明。

1. 浮點數值

  所謂浮點數值,就是該數值中必須包含一個小數點,並且小數點後面必須至少有一位數字。雖然小數點前面可以沒有整數,但我們不推薦這種寫法。以下時浮點數值的幾個例子:

var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1;        // 有效,但不推薦

  由於儲存浮點數值需要的記憶體空間時儲存整數值的兩倍,因此ECMAScript會不失時機地將浮點數值轉換為整數值。顯然,如果小數點後面沒有跟任何數字,那麼這個數值本身表示的就是一個整數(如1.0),那麼該值也會被轉換為整數,如下面的例子所示:

vae floatNum1 = 1;        // 小數點後面沒有數字——解析為1
var floatNum2 = 10.0;    // 整數——解析為10

  對於那些極大或極小的數值,可以用e表示法(即科學計數法)表示的浮點數值表示。用e表示法表示的數值等於e前面的數值乘以10的指數次冪。ECMAScript中e表示法的格式也是如此,即前面時一個數值,(可以是整數也可以是浮點數),中間時一個大寫或小寫的字母E,後面時10的冪中的指數,該冪值將用來與前面的數相乘。下面是一個使用e表示法表示數值的例子:

var floatNum = 3.125e7;        // 等於31250000

  在這個例子中,使用e表示法表示的變數floatNum的形式雖然簡潔,但它的實際值則是31250000。在此,e表示法的實際含義就是“ 3.125乘以107”。

  也可以使用e表示法表示極小的數值,如 0.00000000000000003,這個數值可以使用更簡潔的3e-17表示。在預設情況下,ECMAScript會將那些小數點後面帶有6個零以上的浮點數轉換為以e表示法表示的數值(例如,0.0000003將會別轉換成3e-7)。

  浮點數值得最高精度是17位小數,但在進行算術計算時,其精確度遠遠不如整數。例如,0.1加0.2得結果不是0.3,而是0.30000000000000004。這個小小的舍入誤差會導致無法測試特定的浮點數值。例如:

if (a + b == 0.3) {        // 不要做這樣的測試!
    alert("You got 0.3.");
}

  在這個例子中,我們測試的是兩個數的和是不是等於0.3。如果這兩個數是0.05he0.25,或者是0.15和0.15都不會有問題。而如前所述,如果這兩個是0.1和0.2,那麼測試將無法通過。因此,永遠不要測試某個特定的浮點數值。

關於浮點數值計算會產生舍入誤差的問題,有一點需要明確:這是使用基於IEEE754數值的浮點計算的通病,ECMAScript並非獨此一家;其他使用相同數值格式的語言也存在這個問題。

2. 數值範圍

  由於記憶體限制,ECMAScript並不能儲存世界上所有的數值。ECMAScript能夠表示的最小數值儲存在Number.MIN_VALUE中——在大多數瀏覽器中,這個值是5e-324;能夠表示的最大數值儲存在Number.MAX_VALUE中——在大多數瀏覽器中,這個值是1.7976931348623157e+308。如果某次計算的結果得到了一個超出JavaScript數值範圍的值,那麼這個數值將會被自動轉換成特殊的Infinity值。具體來說,如果這個數值是負數,則會被轉換成-Infinity(負無窮)。

  如上所述,如果某次計算返回了正或負的Infinity值,那麼該值將無法參與下一次的計算,因為Infinity不是能夠參與計算的數值。要想確定一個數值是不是有窮的(換句話說,是不是位於最小和最大的數值之間),可以使用 isFinite() 函式。這個函式在引數位於最小與最大之間是會返回true,如下面的例子所示:

var result = Number.MAX_VALUE + Number.MIN_VALUE;
alert(isFinite(result));        // false

  儘管在計算中很少出現某些值超出表示範圍的情況,但在執行極小或極大數值的計算時,檢測監控這些只是可能的,也是必需的。

訪問Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以得到負和正 Infinity 的值。可以想見,這兩個屬性中分別儲存著 -Infinity 和 Infinity。

3.NaN

  NaN,即非數值(Not a Number)是一個特殊的數值,這個數值用於表示一個本來要返回數值的運算元未返回數值的情況(這樣就不會丟擲錯誤了)。例如,在其他程式語言中,任何數值除以非數值都會導致錯誤,從而停止程式碼執行。但在ECMAScript中,任何數值除以非數值會返回NaN,因此不會影響其他程式碼的執行。

  NaN本身有兩個非同尋常的特點。首先,任何涉及NaN的操作(例如NaN/0)都會返回NaN,這個特點在多步計算中可能導致問題。其次,NaN與任何值都不相等,包括NaN本身。例如,下面的程式碼會返回false:

alert(NaN == NaN);        // false

  針對NaN的這兩個特點,ECMAScript定義了isNaN()函式。這個函式接受一個引數,該引數可以時任何型別,而函式會幫我們確定這個引數是否“不是數值“。isNaN()在接收到一個值以後,會嘗試將這個值轉換為數值。某些不是數值的值會直接轉換為數值,例如字串“10”或Boolean值。而任何不能被轉換為數值的值都會導致這個函式返回true。請看下面的例子:

alert(isNaN(NaN));        // true
alert(isNaN(10));         // false (10是一個數值)
alert(isNaN("10"));       // false (可以被轉換成數值10)
alert(isNaN("blue"));     // true (不能被轉換成數值)
alert(isNaN(true));       // false (可以被轉換成數值1)

  這個例子測試了5各不同的值。測試的第一個只是NaN本身,結果當然會返回true。然後分別測試了數值10和字串“10”,結果這兩個側式都返回了false,因為前者本身就是數值,而後者可以唄轉換成數值。但是,字串“blue”不能被轉換成數值,因此函式返回了true。由於Boolean值true可以轉換成數值1,因此函式返回false。

儘管有點兒不可思議,但isNaN()確實也適用於物件。在基於物件呼叫isNaN()函式時,會首先呼叫物件的valueOf()方法,然後確定該方法返回的值是否可以轉換為數值。如果不能,則基於這個返回值再呼叫toString()方法,再測試返回值。而這個過程也是ECMAScript中內建函式和操作符的一般執行流程,更詳細的內容參見後文。

4.數值轉換

  有3個函式可以把非數值轉換為數值:Number()、parseInt() 和 parseFloat() 。第一個函式,即轉型函式Number()可以用於任何資料型別,而另外兩個函式則專門把字串轉換成數值。這3個函式對於同樣的輸入會有返回不同的結果。

  Number()函式的轉換規則如下:

  • 如果是Boolean值,true 和 false 將分別被轉換成 1 和 0。
  • 如果是數字值,只是簡單的傳入和返回。
  • 如果是null值,返回0。
  • 如果是undefined值,返回NaN。
  • 如果是字串,遵循以下規則:    
    1. 如果字串中只包含數字(包括前面帶正號或負號的情況),則將其轉換為十進位制數值,即“1”會變成 1,“123”會變成 123,而“011”會變成11(注意:前導的零被忽略了);
    2. 如果字串中含有效的浮點格式,如“1.1”,則將其轉換為對應的浮點數值(同樣,也會忽略前導零)
    3. 如果字串中包含有效的十六進位制格式,例如“0xf",則將其轉換為相同大小的十進位制整數值;
    4. 如果字串時空的(不包含任何字元),則將其轉換為0;
    5. 如果字串中包含出上述格式之外的字元,則將其轉換為NaN。
  • 如果是物件,則呼叫物件的valueOf()方法,然後依照前面的規則轉換返回的值。如果轉換的結果是NaN,則呼叫物件的toSting()方法,然後再次依照前面的規則轉換返回的字串值。

  根據這麼多的規則使用Number()把各種資料型別轉換為數值,確實有點複雜。下面還是給出幾個具體的例子吧。

var num1 = Number("Hello World");        // NaN
var num2 = Number(" ");                  // 0
var num3 = Number("000011");             // 11
var num3 = Number(true);                 // 1

  首先,字串”Hello World!"會被轉換為NaN,因為其中不包含任何有意義的數字值。空字串會被轉換為0.字串”00011“會被轉換為11,因為忽略了其前導的零。最後,true值被轉換成1。

一元加操作符的操作與Number()函式相同。

  由於Number()函式在轉換字串時比較複雜而且不夠合理,因此在處理整數的時候更常用的是parseInt() 。parseInt()函式在轉換字串時,更多的是看其是否符合數值模式。它會忽略字串前面的空格,直至找到第一個非空格字元。如果第一個字元不是數字字元或者負號,parseInt()就會返回NaN;也就是說,用 parseInt() 轉換空字串會返回NaN(Number()對空字串返回0)。如果第一個字元是數字字元,parseInt() 會繼續解析第二個字元,直接解析完所有後續字元或者遇到了一個非數字字元。例如,”1234blue"會被轉換為1234,因為“blue"會被完全忽略。類似地,”22.5“會被轉換為22,因為小數點並不是有效的數字字元。

  如果字串中的第一個字元是數字字元,parseInt() 也能夠識別出個種整數格式(即前面討論的十進位制,八進位制和十六進位制數)。也就是說,如果字串以”0x"開頭且後面跟著數字字元,就會將其當作一個十六進位制整數;如果字串以“0”開頭且後跟數字字元,則會將其當作一個八進位制數來解析。

  為了更好的理解 parseInt() 函式的轉換規則,下面給出一些例子:

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 (十六進位制數)

  在使用 parseInt() 解析像八進位制字面量的字串時,ECMAScript3 和 5 存在分歧。例如:

// ECMAScript 3 認為是 56 (八進位制), ECMAScript 5 認為是 70 (十進位制)
var num = parseInt("070");

  在 ECMAScript 3 JavaScript 引擎中,“070”被當成八進位制字面量,因此轉換後的值是十進位制的56。而在ECMAScript 5 JavaScript 引擎中,parseInt() 已經不具有解析八進位制值得能力,因此前導的零會被認為無效,從而將這個值當成“70”,結果就得到十進位制得70。在ECMAScript5 中,即使是在非嚴格模式下也會如此。

  為了消除在使用 parseInt() 函式是可能導致得上述困惑,可以為這個函式提供第二個引數:轉換時使用得基數(即多少進位制)。如果知道要解析得值是十六進位制格式得字串,那麼指定基數16作為第二個引數,可以保證得到正確得結果,例如:

var num = parseInt("0xAF", 16);     // 175

  實際上,如果指定了16作為第二個引數,字串可以不帶前面的“0x”,如下所示:

var num1 = parseInt("AF", 16);        // 175
var num2 = parseInt("AF");            // NaN

  這個例子中的第一個轉換成功了,而第二個則失敗了。差別在於第一個轉換傳入了基數,明確告訴 parseInt() 要解析一個十六進位制格式的字串;而第二個轉換髮現第一個字元不是數字字元,因此就自動終止了。

  指定基數會影響到轉換的輸出結果。例如:

var num1 = parseInt("10", 2);        // 2 (按二進位制解析)
var num2 = parseInt("10", 8);        // 8 (按八進位制解析)
var num3 = parseInt("10", 10);       // 10 (按十進位制解析)
var num4 = parseInt("10", 16);       // 16 (按十六進位制解析)

  不指定基數意味著讓 parseInt() 決定如何解析輸入的字串,因此為了避免錯誤的解析,我們建議無論什麼情況下都明確指定基數。

多數情況下,我們要解析的都是十進位制的數值,因此始終將10作為第二個引數是非常必要的。

  與 parseInt() 函式類似,parseFloat() 也是從第一個字元(位置0)開始解析每個字元。而且也是一直解析到字串尾,或者解析到遇見一個無效的浮點數字字元為止。也就是說,字串中的第一個小數點是有效的,而第二個小輸掉就是無效的了,因此它後面的字串將被忽略。舉例來說,“22.34.5”將會被轉換為 22.34 。

  除了第一個小數點有效之外,parseInt() 與 parseFloat() 的第二個區別在於它始終都會忽略前導的零。parseFloat() 可以識別前面討論過的所有浮點數值格式,也包括十進位制整數格式。但十六進位制格式的字串則始終會被轉換成0 。由於 parseFlaot() 只解析十進位制值,因此它沒有用第二個引數指定技術的用法。最後還要注意一點:如果字串包含的是一個可解析為整數的數(沒有小數點,或者小數點後都是零),parseFloat() 會返回整數。以下是使用 parseFloat() 轉換數值的幾個典型示例:

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