本文參考JavaScript高級(jí)程序設(shè)計(jì)第四版 第三章 語言基礎(chǔ) 3.4.5節(jié)
一、Number類型
1.八進(jìn)制和十六進(jìn)制
八進(jìn)制第一位必須是0囊嘉,第二位必須是0-7.超出范圍將做為十進(jìn)制來解析寒随。
let octalNum1 = 070;//八進(jìn)制56
let octalNum2 = 079;//無效八進(jìn)制瘩绒,解析為79
let octalNum3 = 08;//無效八進(jìn)制,解析為8
十六進(jìn)制前兩位必須是0x,后跟0-9或者A-F(大小寫均可)
let hexNum1 = 0xA; // 十六進(jìn)制 10
let hexNum2 = 0x1f; // 十六進(jìn)制 31
2.小數(shù)
因?yàn)榇鎯?chǔ)浮點(diǎn)值使用的內(nèi)存空間是存儲(chǔ)整數(shù)值的兩倍嗤谚,所以 ECMAScript 總是想方設(shè)法把值轉(zhuǎn)換為整數(shù)。在小數(shù)點(diǎn)后面沒有數(shù)字的情況下泥张,數(shù)值就會(huì)變成整數(shù)呵恢。類似地,如果數(shù)值本身就是整數(shù)媚创,只是小數(shù)點(diǎn)后面跟著 0(如 1.0),那它也會(huì)被轉(zhuǎn)換為整數(shù)彤恶,如下例所示:
let floatNum1 = 1.; // 小數(shù)點(diǎn)后面沒有數(shù)字钞钙,當(dāng)成整數(shù) 1 處理
let floatNum2 = 10.0; // 小數(shù)點(diǎn)后面是零,當(dāng)成整數(shù) 10 處理
對(duì)于非常大或非常小的數(shù)值声离,浮點(diǎn)值可以用科學(xué)記數(shù)法來表示芒炼。科學(xué)記數(shù)法用于表示一個(gè)應(yīng)該乘以10 的給定次冪的數(shù)值术徊。ECMAScript 中科學(xué)記數(shù)法的格式要求是一個(gè)數(shù)值(整數(shù)或浮點(diǎn)數(shù))后跟一個(gè)大寫或小寫的字母 e本刽,再加上一個(gè)要乘的 10 的多少次冪。比如:
let floatNum = 3.125e7; // 等于 31250000
在這個(gè)例子中赠涮,floatNum 等于 31 250 000子寓,只不過科學(xué)記數(shù)法顯得更簡潔。這種表示法實(shí)際上相當(dāng)于說:“以 3.125 作為系數(shù)笋除,乘以 10 的 7 次冪斜友。”
科學(xué)記數(shù)法也可以用于表示非常小的數(shù)值垃它,例如 0.000 000 000 000 000 03鲜屏。這個(gè)數(shù)值用科學(xué)記數(shù)法可以表示為 3e-17烹看。默認(rèn)情況下,ECMAScript 會(huì)將小數(shù)點(diǎn)后至少包含 6 個(gè)零的浮點(diǎn)值轉(zhuǎn)換為科學(xué)記數(shù)法(例如洛史,0.000 000 3 會(huì)被轉(zhuǎn)換為 3e-7)惯殊。
浮點(diǎn)值的精確度最高可達(dá) 17 位小數(shù),但在算術(shù)計(jì)算中遠(yuǎn)不如整數(shù)精確也殖。例如土思,0.1 加 0.2 得到的不是 0.3,而是 0.300 000 000 000 000 04毕源。由于這種微小的舍入錯(cuò)誤浪漠,導(dǎo)致很難測試特定的浮點(diǎn)值。比如下面的例子:
if (a + b == 0.3) { // 別這么干霎褐!
console.log("You got 0.3.");
}
這里檢測兩個(gè)數(shù)值之和是否等于 0.3址愿。如果兩個(gè)數(shù)值分別是 0.05 和 0.25,或者 0.15 和 0.15冻璃,那沒問題响谓。但如果是 0.1 和 0.2,如前所述省艳,測試將失敗娘纷。因此永遠(yuǎn)不要測試某個(gè)特定的浮點(diǎn)值。
擴(kuò)展參考js 小數(shù)的精度損失
3.NAN
有一個(gè)特殊的數(shù)值叫 NaN跋炕,意思是“不是數(shù)值”(Not a Number)赖晶,用于表示本來要返回?cái)?shù)值的操作失敗了(而不是拋出錯(cuò)誤)。比如辐烂,用 0 除任意數(shù)值在其他語言中通常都會(huì)導(dǎo)致錯(cuò)誤遏插,從而中止代碼執(zhí)行。但在 ECMAScript 中纠修,0胳嘲、+0 或-0 相除會(huì)返回 NaN:
console.log(0/0); // NaN
console.log(-0/+0); // NaN
如果分子是非 0 值,分母是有符號(hào) 0 或無符號(hào) 0扣草,則會(huì)返回 Infinity 或-Infinity:
console.log(5/0); // Infinity
console.log(5/-0); // -Infinity
NaN 有幾個(gè)獨(dú)特的屬性了牛。首先,任何涉及 NaN 的操作始終返回 NaN(如 NaN/10)辰妙,在連續(xù)多步計(jì)算時(shí)這可能是個(gè)問題鹰祸。其次,NaN 不等于包括 NaN 在內(nèi)的任何值上岗。例如福荸,下面的比較操作會(huì)返回 false:
console.log(NaN == NaN); // false
為此,ECMAScript 提供了 isNaN()函數(shù)肴掷。該函數(shù)接收一個(gè)參數(shù)敬锐,可以是任意數(shù)據(jù)類型背传,然后判斷這個(gè)參數(shù)是否“不是數(shù)值”。把一個(gè)值傳給 isNaN()后台夺,該函數(shù)會(huì)嘗試把它轉(zhuǎn)換為數(shù)值径玖。某些非數(shù)值的值可以直接轉(zhuǎn)換成數(shù)值,如字符串"10"或布爾值颤介。任何不能轉(zhuǎn)換為數(shù)值的值都會(huì)導(dǎo)致這個(gè)函數(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 個(gè)不同的值辕近。首先測試的是 NaN 本身韵吨,顯然會(huì)返回 true。接著測試了數(shù)值 10和字符串"10"移宅,都返回 false,因?yàn)樗鼈兊臄?shù)值都是 10漏峰。字符串"blue"不能轉(zhuǎn)換為數(shù)值糠悼,因此函數(shù)返回 true浅乔。布爾值 true 可以轉(zhuǎn)換為數(shù)值 1靖苇,因此返回 false。
4.數(shù)值轉(zhuǎn)換
有 3 個(gè)函數(shù)可以將非數(shù)值轉(zhuǎn)換為數(shù)值:Number()芯砸、parseInt()和 parseFloat()假丧。Number()是轉(zhuǎn)型函數(shù),可用于任何數(shù)據(jù)類型动羽。后兩個(gè)函數(shù)主要用于將字符串轉(zhuǎn)換為數(shù)值包帚。對(duì)于同樣的參數(shù),這 3 個(gè)函數(shù)執(zhí)行的操作也不同运吓。
5.Number()函數(shù)
Number()函數(shù)基于如下規(guī)則執(zhí)行轉(zhuǎn)換渴邦。
- 布爾值疯趟,true 轉(zhuǎn)換為 1,false 轉(zhuǎn)換為 0谋梭。
- 數(shù)值信峻,直接返回。
- null瓮床,返回 0盹舞。
- undefined,返回 NaN隘庄。
- 字符串踢步,應(yīng)用以下規(guī)則。
- 如果字符串包含數(shù)值字符丑掺,包括數(shù)值字符前面帶加获印、減號(hào)的情況,則轉(zhuǎn)換為一個(gè)十進(jìn)制數(shù)值吼鱼。因此蓬豁,Number("1")返回 1,Number("123")返回 123菇肃,Number("011")返回 11(忽略前面的零)地粪。
- 如果字符串包含有效的浮點(diǎn)值格式如"1.1",則會(huì)轉(zhuǎn)換為相應(yīng)的浮點(diǎn)值(同樣琐谤,忽略前面的零)蟆技。
- 如果字符串包含有效的十六進(jìn)制格式如"0xf",則會(huì)轉(zhuǎn)換為與該十六進(jìn)制值對(duì)應(yīng)的十進(jìn)制整數(shù)值斗忌。
- 如果是空字符串(不包含字符)质礼,則返回 0。 如果字符串包含除上述情況之外的其他字符织阳,則返回 NaN眶蕉。
- 對(duì)象,調(diào)用 valueOf()方法唧躲,并按照上述規(guī)則轉(zhuǎn)換返回的值造挽。如果轉(zhuǎn)換結(jié)果是 NaN,則調(diào)用toString()方法弄痹,再按照轉(zhuǎn)換字符串的規(guī)則轉(zhuǎn)換饭入。
從不同數(shù)據(jù)類型到數(shù)值的轉(zhuǎn)換有時(shí)候會(huì)比較復(fù)雜,看一看 Number()的轉(zhuǎn)換規(guī)則就知道了肛真。下面是幾個(gè)具體的例子:
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,因?yàn)樗也坏綄?duì)應(yīng)的數(shù)值∏溃空字符串轉(zhuǎn)換后是 0讥珍。字符串 000011 轉(zhuǎn)換后是 11,因?yàn)榍懊娴牧惚缓雎粤朔苟W詈蟠觯瑃rue 轉(zhuǎn)換為 1。
注意 本章后面會(huì)討論到的一元加操作符與 Number()函數(shù)遵循相同的轉(zhuǎn)換規(guī)則寞肖。
6.parseInt()
考慮到用 Number()函數(shù)轉(zhuǎn)換字符串時(shí)相對(duì)復(fù)雜且有點(diǎn)反常規(guī)纲酗,通常在需要得到整數(shù)時(shí)可以優(yōu)先使用 parseInt()函數(shù)。
parseInt()函數(shù)更專注于字符串是否包含數(shù)值模式新蟆。字符串最前面的空格會(huì)被忽略觅赊,從第一個(gè)非空格字符開始轉(zhuǎn)換。如果第一個(gè)字符不是數(shù)值字符琼稻、加號(hào)或減號(hào)吮螺,parseInt()立即返回 NaN。這意味著空字符串也會(huì)返回 NaN(這一點(diǎn)跟 Number()不一樣帕翻,它返回 0)鸠补。如果第一個(gè)字符是數(shù)值字符、加號(hào)或減號(hào)嘀掸,則繼續(xù)依次檢測每個(gè)字符紫岩,直到字符串末尾,或碰到非數(shù)值字符睬塌。比如泉蝌,"1234blue"會(huì)被轉(zhuǎn)換為 1234,因?yàn)?blue"會(huì)被完全忽略揩晴。類似地勋陪,"22.5"會(huì)被轉(zhuǎn)換為 22,因?yàn)樾?shù)點(diǎn)不是有效的整數(shù)字符硫兰。
假設(shè)字符串中的第一個(gè)字符是數(shù)值字符诅愚,parseInt()函數(shù)也能識(shí)別不同的整數(shù)格式(十進(jìn)制、八進(jìn)制劫映、十六進(jìn)制)呻粹。換句話說,如果字符串以"0x"開頭苏研,就會(huì)被解釋為十六進(jìn)制整數(shù)。如果字符串以"0"開頭腮郊,且緊跟著數(shù)值字符摹蘑,在非嚴(yán)格模式下會(huì)被某些實(shí)現(xiàn)解釋為八進(jìn)制整數(shù)。
下面幾個(gè)轉(zhuǎn)換示例有助于理解上述規(guī)則:
let num1 = parseInt("1234blue"); // 1234
let num2 = parseInt(""); // NaN
let num3 = parseInt("0xA"); // 10轧飞,解釋為十六進(jìn)制整數(shù)
let num4 = parseInt(22.5); // 22
let num5 = parseInt("70"); // 70衅鹿,解釋為十進(jìn)制值
let num6 = parseInt("0xf"); // 15撒踪,解釋為十六進(jìn)制整數(shù)
不同的數(shù)值格式很容易混淆,因此 parseInt()也接收第二個(gè)參數(shù)大渤,用于指定底數(shù)(進(jìn)制數(shù))制妄。如果知道要解析的值是十六進(jìn)制,那么可以傳入 16 作為第二個(gè)參數(shù)泵三,以便正確解析:
let num = parseInt("0xAF", 16); // 175
事實(shí)上耕捞,如果提供了十六進(jìn)制參數(shù),那么字符串前面的"0x"可以省掉:
let num1 = parseInt("AF", 16); // 175
let num2 = parseInt("AF"); // NaN
在這個(gè)例子中烫幕,第一個(gè)轉(zhuǎn)換是正確的俺抽,而第二個(gè)轉(zhuǎn)換失敗了。區(qū)別在于第一次傳入了進(jìn)制數(shù)作為參數(shù)较曼,告訴 parseInt()要解析的是一個(gè)十六進(jìn)制字符串磷斧。而第二個(gè)轉(zhuǎn)換檢測到第一個(gè)字符就是非數(shù)值字符,隨即自動(dòng)停止并返回 NaN捷犹。
通過第二個(gè)參數(shù)弛饭,可以極大擴(kuò)展轉(zhuǎn)換后獲得的結(jié)果類型。比如:
let num1 = parseInt("10", 2); // 2萍歉,按二進(jìn)制解析
let num2 = parseInt("10", 8); // 8侣颂,按八進(jìn)制解析
let num3 = parseInt("10", 10); // 10,按十進(jìn)制解析
let num4 = parseInt("10", 16); // 16翠桦,按十六進(jìn)制解析
因?yàn)椴粋鞯讛?shù)參數(shù)相當(dāng)于讓 parseInt()自己決定如何解析横蜒,所以為避免解析出錯(cuò),建議始終傳給它第二個(gè)參數(shù)销凑。注意 多數(shù)情況下解析的應(yīng)該都是十進(jìn)制數(shù)丛晌,此時(shí)第二個(gè)參數(shù)就要傳入 10。
7.parseFloat()
parseFloat()函數(shù)的工作方式跟 parseInt()函數(shù)類似斗幼,都是從位置 0 開始檢測每個(gè)字符澎蛛。同樣,它也是解析到字符串末尾或者解析到一個(gè)無效的浮點(diǎn)數(shù)值字符為止蜕窿。這意味著第一次出現(xiàn)的小數(shù)點(diǎn)是有效的谋逻,但第二次出現(xiàn)的小數(shù)點(diǎn)就無效了,此時(shí)字符串的剩余字符都會(huì)被忽略桐经。因此毁兆,"22.34.5"將轉(zhuǎn)換成 22.34。
parseFloat()函數(shù)的另一個(gè)不同之處在于阴挣,它始終忽略字符串開頭的零气堕。這個(gè)函數(shù)能識(shí)別前面討論的所有浮點(diǎn)格式,以及十進(jìn)制格式(開頭的零始終被忽略)。十六進(jìn)制數(shù)值始終會(huì)返回 0茎芭。因?yàn)閜arseFloat()只解析十進(jìn)制值揖膜,因此不能指定底數(shù)。最后梅桩,如果字符串表示整數(shù)(沒有小數(shù)點(diǎn)或者小數(shù)點(diǎn)后面只有一個(gè)零)壹粟,則 parseFloat()返回整數(shù)。下面是幾個(gè)示例:
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 默認(rèn)接收兩個(gè)參數(shù)
第一個(gè)參數(shù)是默認(rèn)是 string 類型值犀呼,如果不是幸撕,會(huì)通過抽象的 ToString 強(qiáng)制轉(zhuǎn)化成 string 類型的值。這其中就會(huì)有強(qiáng)制類型轉(zhuǎn)換過程中的各種坑外臂。
第二個(gè)參數(shù)是 number 類型的進(jìn)制坐儿,如果不是,會(huì)通過抽象的 ToNumber 強(qiáng)制轉(zhuǎn)化成 number 類型的值宋光,范圍是 2-36貌矿,通過強(qiáng)制類型轉(zhuǎn)換后如果是其他值會(huì)返回 NaN。在 ES5 之前如果沒有傳入這個(gè)參數(shù)罪佳,會(huì)根據(jù)第一個(gè)參數(shù)的開頭來判斷進(jìn)制逛漫,0 開頭的字符串會(huì)判斷成八進(jìn)制,也就是 @貘吃饃香 提到的老黃歷坑赘艳。ES5 之后已經(jīng)解決酌毡,不傳這個(gè)參數(shù)默認(rèn)十進(jìn)制。但是這個(gè)參數(shù)容易被忽略蕾管,尤其是在和 map 之類的也容易忽略后續(xù)可選參數(shù)的函數(shù)搭配使用的時(shí)候枷踏,比如['10', '10', '10', '10'].map(parseInt) // 結(jié)果是 [10, NaN, 2, 3]
所以如果只是用 parseInt 來 “取整”,一個(gè)良好的習(xí)慣是永遠(yuǎn)記得設(shè)置第二個(gè)參數(shù)為 10
然后就是盡量不要拿 parseInt 去轉(zhuǎn)換一些其他類型的值掰曾,如果實(shí)在遇到了需要判斷結(jié)果(比如一些閑的蛋疼的面試官非得要考察這種)那就先對(duì)兩個(gè)參數(shù)進(jìn)行求值旭蠕,并轉(zhuǎn)換成相應(yīng)的類型,然后判斷旷坦。判斷的過程可以大致理解為:第一個(gè)參數(shù)的轉(zhuǎn)換結(jié)果去除空白掏熬,然后從左往右提取出在第二個(gè)參數(shù)指定的進(jìn)制下能夠理解的整數(shù)部分,并返回這個(gè)值在十進(jìn)制下的值秒梅。如果轉(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是無效的八進(jìn)制)
parseInt("09")//0(9是無效的八進(jìn)制)
parseInt("0xA")//10
parseInt("0xf")//15
三、參考為什么 parseInt(0.0000008) === 8捆蜀?
parseInt接受兩個(gè)參數(shù)岗屏,第一個(gè)參數(shù)是要轉(zhuǎn)換的字符串(忽略空白)辆琅;第二個(gè)參數(shù)是多少進(jìn)制。建議總是加上第二個(gè)參數(shù)这刷。
parseInt(1000000000000000000000.5, 10); // 1
為什么會(huì)這樣呢?
parseInt 的第一個(gè)類型是字符串娩井,所以會(huì)將傳入的參數(shù)轉(zhuǎn)換成字符串暇屋,也就是String(1000000000000000000000.5) 的結(jié)果為 '1e+21'。parseInt 并沒有將 'e' 視為一個(gè)數(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í)扬霜,會(huì)變成科學(xué)記號(hào)法定鸟,再對(duì)這個(gè)數(shù)進(jìn)行 parseInt操作就會(huì)導(dǎo)致這個(gè)問題發(fā)生。
結(jié)論:不要將 parseInt 當(dāng)做轉(zhuǎn)換 Number 和 Integer 的工具著瓶。
再補(bǔ)上一些悲劻琛:
parseInt(1/0, 19); // 18
parseInt(false, 16); // 250
parseInt(parseInt, 16); // 15
parseInt("0x10"); // 16
parseInt("10", 2); // 2
1.parseFloat
遇到第一個(gè)小數(shù)點(diǎn)有效,后面的無效材原。parseFloat沒有第二個(gè)參數(shù)沸久,只解析十進(jìn)制
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