js Number parseInt parseFloat

本文參考JavaScript高級程序設計第四版 第三章 語言基礎(chǔ) 3.4.5節(jié)

一、Number類型
1.八進制和十六進制

八進制第一位必須是0,第二位必須是0-7.超出范圍將做為十進制來解析。

let octalNum1 = 070;//八進制56
let octalNum2 = 079;//無效八進制,解析為79
let octalNum3 = 08;//無效八進制,解析為8

十六進制前兩位必須是0x,后跟0-9或者A-F(大小寫均可)

let hexNum1 = 0xA; // 十六進制 10 
let hexNum2 = 0x1f; // 十六進制 31
2.小數(shù)

因為存儲浮點值使用的內(nèi)存空間是存儲整數(shù)值的兩倍,所以 ECMAScript 總是想方設法把值轉(zhuǎn)換為整數(shù)。在小數(shù)點后面沒有數(shù)字的情況下,數(shù)值就會變成整數(shù)。類似地,如果數(shù)值本身就是整數(shù),只是小數(shù)點后面跟著 0(如 1.0),那它也會被轉(zhuǎn)換為整數(shù),如下例所示:

let floatNum1 = 1.; // 小數(shù)點后面沒有數(shù)字,當成整數(shù) 1 處理
let floatNum2 = 10.0; // 小數(shù)點后面是零,當成整數(shù) 10 處理

對于非常大或非常小的數(shù)值,浮點值可以用科學記數(shù)法來表示??茖W記數(shù)法用于表示一個應該乘以10 的給定次冪的數(shù)值。ECMAScript 中科學記數(shù)法的格式要求是一個數(shù)值(整數(shù)或浮點數(shù))后跟一個大寫或小寫的字母 e,再加上一個要乘的 10 的多少次冪。比如:

let floatNum = 3.125e7; // 等于 31250000

在這個例子中,floatNum 等于 31 250 000,只不過科學記數(shù)法顯得更簡潔。這種表示法實際上相當于說:“以 3.125 作為系數(shù),乘以 10 的 7 次冪。”

科學記數(shù)法也可以用于表示非常小的數(shù)值,例如 0.000 000 000 000 000 03。這個數(shù)值用科學記數(shù)法可以表示為 3e-17。默認情況下,ECMAScript 會將小數(shù)點后至少包含 6 個零的浮點值轉(zhuǎn)換為科學記數(shù)法(例如,0.000 000 3 會被轉(zhuǎn)換為 3e-7)。

浮點值的精確度最高可達 17 位小數(shù),但在算術(shù)計算中遠不如整數(shù)精確。例如,0.1 加 0.2 得到的不是 0.3,而是 0.300 000 000 000 000 04。由于這種微小的舍入錯誤,導致很難測試特定的浮點值。比如下面的例子:

if (a + b == 0.3) { // 別這么干! 
 console.log("You got 0.3."); 
} 

這里檢測兩個數(shù)值之和是否等于 0.3。如果兩個數(shù)值分別是 0.05 和 0.25,或者 0.15 和 0.15,那沒問題。但如果是 0.1 和 0.2,如前所述,測試將失敗。因此永遠不要測試某個特定的浮點值。

擴展參考js 小數(shù)的精度損失

3.NAN

有一個特殊的數(shù)值叫 NaN,意思是“不是數(shù)值”(Not a Number),用于表示本來要返回數(shù)值的操作失敗了(而不是拋出錯誤)。比如,用 0 除任意數(shù)值在其他語言中通常都會導致錯誤,從而中止代碼執(zhí)行。但在 ECMAScript 中,0、+0 或-0 相除會返回 NaN:

console.log(0/0); // NaN 
console.log(-0/+0); // NaN

如果分子是非 0 值,分母是有符號 0 或無符號 0,則會返回 Infinity 或-Infinity:

console.log(5/0); // Infinity 
console.log(5/-0); // -Infinity

NaN 有幾個獨特的屬性。首先,任何涉及 NaN 的操作始終返回 NaN(如 NaN/10),在連續(xù)多步計算時這可能是個問題。其次,NaN 不等于包括 NaN 在內(nèi)的任何值。例如,下面的比較操作會返回 false:

console.log(NaN == NaN); // false 

為此,ECMAScript 提供了 isNaN()函數(shù)。該函數(shù)接收一個參數(shù),可以是任意數(shù)據(jù)類型,然后判斷這個參數(shù)是否“不是數(shù)值”。把一個值傳給 isNaN()后,該函數(shù)會嘗試把它轉(zhuǎn)換為數(shù)值。某些非數(shù)值的值可以直接轉(zhuǎn)換成數(shù)值,如字符串"10"或布爾值。任何不能轉(zhuǎn)換為數(shù)值的值都會導致這個函數(shù)返回true。舉例如下:

console.log(isNaN(NaN)); // true 
console.log(isNaN(10)); // false,10 是數(shù)值
console.log(isNaN("10")); // false,可以轉(zhuǎn)換為數(shù)值 10 
console.log(isNaN("blue")); // true,不可以轉(zhuǎn)換為數(shù)值
console.log(isNaN(true)); // false,可以轉(zhuǎn)換為數(shù)值 1 

上述的例子測試了 5 個不同的值。首先測試的是 NaN 本身,顯然會返回 true。接著測試了數(shù)值 10和字符串"10",都返回 false,因為它們的數(shù)值都是 10。字符串"blue"不能轉(zhuǎn)換為數(shù)值,因此函數(shù)返回 true。布爾值 true 可以轉(zhuǎn)換為數(shù)值 1,因此返回 false。

4.數(shù)值轉(zhuǎn)換

有 3 個函數(shù)可以將非數(shù)值轉(zhuǎn)換為數(shù)值:Number()、parseInt()和 parseFloat()。Number()是轉(zhuǎn)型函數(shù),可用于任何數(shù)據(jù)類型。后兩個函數(shù)主要用于將字符串轉(zhuǎn)換為數(shù)值。對于同樣的參數(shù),這 3 個函數(shù)執(zhí)行的操作也不同。

5.Number()函數(shù)

Number()函數(shù)基于如下規(guī)則執(zhí)行轉(zhuǎn)換。

  • 布爾值,true 轉(zhuǎn)換為 1,false 轉(zhuǎn)換為 0。
  • 數(shù)值,直接返回。
  • null,返回 0。
  • undefined,返回 NaN。
  • 字符串,應用以下規(guī)則。
    • 如果字符串包含數(shù)值字符,包括數(shù)值字符前面帶加、減號的情況,則轉(zhuǎn)換為一個十進制數(shù)值。因此,Number("1")返回 1,Number("123")返回 123,Number("011")返回 11(忽略前面的零)。
    • 如果字符串包含有效的浮點值格式如"1.1",則會轉(zhuǎn)換為相應的浮點值(同樣,忽略前面的零)。
    • 如果字符串包含有效的十六進制格式如"0xf",則會轉(zhuǎn)換為與該十六進制值對應的十進制整數(shù)值。
    • 如果是空字符串(不包含字符),則返回 0。 如果字符串包含除上述情況之外的其他字符,則返回 NaN。
  • 對象,調(diào)用 valueOf()方法,并按照上述規(guī)則轉(zhuǎn)換返回的值。如果轉(zhuǎn)換結(jié)果是 NaN,則調(diào)用toString()方法,再按照轉(zhuǎn)換字符串的規(guī)則轉(zhuǎn)換。

從不同數(shù)據(jù)類型到數(shù)值的轉(zhuǎn)換有時候會比較復雜,看一看 Number()的轉(zhuǎn)換規(guī)則就知道了。下面是幾個具體的例子:

let num1 = Number("Hello world!"); // NaN 
let num2 = Number(""); // 0 
let num3 = Number("000011"); // 11 
let num4 = Number(true); // 1 

可以看到,字符串"Hello world"轉(zhuǎn)換之后是 NaN,因為它找不到對應的數(shù)值??兆址D(zhuǎn)換后是 0。字符串 000011 轉(zhuǎn)換后是 11,因為前面的零被忽略了。最后,true 轉(zhuǎn)換為 1。

注意 本章后面會討論到的一元加操作符與 Number()函數(shù)遵循相同的轉(zhuǎn)換規(guī)則。

6.parseInt()

考慮到用 Number()函數(shù)轉(zhuǎn)換字符串時相對復雜且有點反常規(guī),通常在需要得到整數(shù)時可以優(yōu)先使用 parseInt()函數(shù)。

parseInt()函數(shù)更專注于字符串是否包含數(shù)值模式。字符串最前面的空格會被忽略,從第一個非空格字符開始轉(zhuǎn)換。如果第一個字符不是數(shù)值字符、加號或減號,parseInt()立即返回 NaN。這意味著空字符串也會返回 NaN(這一點跟 Number()不一樣,它返回 0)。如果第一個字符是數(shù)值字符、加號或減號,則繼續(xù)依次檢測每個字符,直到字符串末尾,或碰到非數(shù)值字符。比如,"1234blue"會被轉(zhuǎn)換為 1234,因為"blue"會被完全忽略。類似地,"22.5"會被轉(zhuǎn)換為 22,因為小數(shù)點不是有效的整數(shù)字符。

假設字符串中的第一個字符是數(shù)值字符,parseInt()函數(shù)也能識別不同的整數(shù)格式(十進制、八進制、十六進制)。換句話說,如果字符串以"0x"開頭,就會被解釋為十六進制整數(shù)。如果字符串以"0"開頭,且緊跟著數(shù)值字符,在非嚴格模式下會被某些實現(xiàn)解釋為八進制整數(shù)。

下面幾個轉(zhuǎn)換示例有助于理解上述規(guī)則:

let num1 = parseInt("1234blue"); // 1234 
let num2 = parseInt(""); // NaN 
let num3 = parseInt("0xA"); // 10,解釋為十六進制整數(shù)
let num4 = parseInt(22.5); // 22 
let num5 = parseInt("70"); // 70,解釋為十進制值
let num6 = parseInt("0xf"); // 15,解釋為十六進制整數(shù)

不同的數(shù)值格式很容易混淆,因此 parseInt()也接收第二個參數(shù),用于指定底數(shù)(進制數(shù))。如果知道要解析的值是十六進制,那么可以傳入 16 作為第二個參數(shù),以便正確解析:

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

事實上,如果提供了十六進制參數(shù),那么字符串前面的"0x"可以省掉:

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

在這個例子中,第一個轉(zhuǎn)換是正確的,而第二個轉(zhuǎn)換失敗了。區(qū)別在于第一次傳入了進制數(shù)作為參數(shù),告訴 parseInt()要解析的是一個十六進制字符串。而第二個轉(zhuǎn)換檢測到第一個字符就是非數(shù)值字符,隨即自動停止并返回 NaN。

通過第二個參數(shù),可以極大擴展轉(zhuǎn)換后獲得的結(jié)果類型。比如:

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

因為不傳底數(shù)參數(shù)相當于讓 parseInt()自己決定如何解析,所以為避免解析出錯,建議始終傳給它第二個參數(shù)。注意 多數(shù)情況下解析的應該都是十進制數(shù),此時第二個參數(shù)就要傳入 10。

7.parseFloat()

parseFloat()函數(shù)的工作方式跟 parseInt()函數(shù)類似,都是從位置 0 開始檢測每個字符。同樣,它也是解析到字符串末尾或者解析到一個無效的浮點數(shù)值字符為止。這意味著第一次出現(xiàn)的小數(shù)點是有效的,但第二次出現(xiàn)的小數(shù)點就無效了,此時字符串的剩余字符都會被忽略。因此,"22.34.5"將轉(zhuǎn)換成 22.34。

parseFloat()函數(shù)的另一個不同之處在于,它始終忽略字符串開頭的零。這個函數(shù)能識別前面討論的所有浮點格式,以及十進制格式(開頭的零始終被忽略)。十六進制數(shù)值始終會返回 0。因為parseFloat()只解析十進制值,因此不能指定底數(shù)。最后,如果字符串表示整數(shù)(沒有小數(shù)點或者小數(shù)點后面只有一個零),則 parseFloat()返回整數(shù)。下面是幾個示例:

let num1 = parseFloat("1234blue"); // 1234,按整數(shù)解析
let num2 = parseFloat("0xA"); // 0 
let num3 = parseFloat("22.5"); // 22.5 
let num4 = parseFloat("22.34.5"); // 22.34 
let num5 = parseFloat("0908.5"); // 908.5 
let num6 = parseFloat("3.125e7"); // 31250000
二、參考js中使用位操作符取整有沒有什么副作用?
1.Number.parseInt 默認接收兩個參數(shù)

第一個參數(shù)是默認是 string 類型值,如果不是,會通過抽象的 ToString 強制轉(zhuǎn)化成 string 類型的值。這其中就會有強制類型轉(zhuǎn)換過程中的各種坑。

第二個參數(shù)是 number 類型的進制,如果不是,會通過抽象的 ToNumber 強制轉(zhuǎn)化成 number 類型的值,范圍是 2-36,通過強制類型轉(zhuǎn)換后如果是其他值會返回 NaN。在 ES5 之前如果沒有傳入這個參數(shù),會根據(jù)第一個參數(shù)的開頭來判斷進制,0 開頭的字符串會判斷成八進制,也就是 @貘吃饃香 提到的老黃歷坑。ES5 之后已經(jīng)解決,不傳這個參數(shù)默認十進制。但是這個參數(shù)容易被忽略,尤其是在和 map 之類的也容易忽略后續(xù)可選參數(shù)的函數(shù)搭配使用的時候,比如['10', '10', '10', '10'].map(parseInt) // 結(jié)果是 [10, NaN, 2, 3] 所以如果只是用 parseInt 來 “取整”,一個良好的習慣是永遠記得設置第二個參數(shù)為 10

然后就是盡量不要拿 parseInt 去轉(zhuǎn)換一些其他類型的值,如果實在遇到了需要判斷結(jié)果(比如一些閑的蛋疼的面試官非得要考察這種)那就先對兩個參數(shù)進行求值,并轉(zhuǎn)換成相應的類型,然后判斷。判斷的過程可以大致理解為:第一個參數(shù)的轉(zhuǎn)換結(jié)果去除空白,然后從左往右提取出在第二個參數(shù)指定的進制下能夠理解的整數(shù)部分,并返回這個值在十進制下的值。如果轉(zhuǎn)換失敗返回 NaN除了這些情況,parseInt 在 JavaScript 數(shù)值允許的范圍內(nèi)都是可以安全使用的

parseInt("1234blue")//1234
parseInt("");//NaN
parseInt(22.5)//22
parseInt("70")//70
parseInt("070")//56
parseInt("08")//0(8是無效的八進制)
parseInt("09")//0(9是無效的八進制)
parseInt("0xA")//10
parseInt("0xf")//15
三、參考為什么 parseInt(0.0000008) === 8?

parseInt接受兩個參數(shù),第一個參數(shù)是要轉(zhuǎn)換的字符串(忽略空白);第二個參數(shù)是多少進制。建議總是加上第二個參數(shù)。

parseInt(1000000000000000000000.5, 10); // 1

為什么會這樣呢?
parseInt 的第一個類型是字符串,所以會將傳入的參數(shù)轉(zhuǎn)換成字符串,也就是String(1000000000000000000000.5) 的結(jié)果為 '1e+21'。parseInt 并沒有將 'e' 視為一個數(shù)字,所以在轉(zhuǎn)換到 1 后就停止了。
這也就可以解釋 parseInt(0.0000008) === 8

String(0.000008); // '0.000008'
String(0.0000008); // '8e-7'

從上面的程式碼可以看出,小于 0.0000001(1e-7) 的數(shù)字轉(zhuǎn)換成 String時,會變成科學記號法,再對這個數(shù)進行 parseInt操作就會導致這個問題發(fā)生。
結(jié)論:不要將 parseInt 當做轉(zhuǎn)換 Number 和 Integer 的工具。
再補上一些悲劇:

parseInt(1/0, 19);      // 18
parseInt(false, 16);    // 250
parseInt(parseInt, 16); // 15
parseInt("0x10");       // 16
parseInt("10", 2);      // 2
1.parseFloat

遇到第一個小數(shù)點有效,后面的無效。parseFloat沒有第二個參數(shù),只解析十進制

parseFloat("22.34.5")//22.34
parseFloat("1234blue")//1234
parseFloat("0xA")//0
parseFloat("22.5")//22.5
parseFloat("0908.5")//908.5
parseFloat("3.125e7")//31250000
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容