在現(xiàn)代 JavaScript 中,數(shù)字(number)有兩種類型:
(253-1)
? 或小于 ?-(253-1)
?。由于僅在少數(shù)特殊領(lǐng)域才會(huì)用到
BigInt,因此我們?cè)谔厥獾恼鹿?jié) BigInt 中對(duì)其進(jìn)行了介紹。所以,在這里我們將討論常規(guī)數(shù)字類型?,F(xiàn)在讓我們開(kāi)始學(xué)習(xí)吧。
假如我們需要表示 10 億。顯然,我們可以這樣寫(xiě):
let billion = 1000000000;
我們也可以使用下劃線 _
作為分隔符:
let billion = 1_000_000_000;
這里的下劃線 _
扮演了“語(yǔ)法糖”的角色,使得數(shù)字具有更強(qiáng)的可讀性。JavaScript 引擎會(huì)直接忽略數(shù)字之間的 _
,所以 上面兩個(gè)例子其實(shí)是一樣的。
但在現(xiàn)實(shí)生活中,我們通常會(huì)盡量避免寫(xiě)帶一長(zhǎng)串零的數(shù)。因?yàn)槲覀儽容^懶……我們會(huì)嘗試將 10 億寫(xiě)成 "1bn"
,或?qū)?73 億寫(xiě)成 "7.3bn"
。對(duì)于大多數(shù)大的數(shù)字來(lái)說(shuō)都是如此。
在 JavaScript 中,我們可以通過(guò)在數(shù)字后面附加字母 "e"
并指定零的個(gè)數(shù)來(lái)縮短數(shù)字:
let billion = 1e9; // 10 億,字面意思:數(shù)字 1 后面跟 9 個(gè) 0
alert( 7.3e9 ); // 73 億(與 7300000000 和 7_300_000_000 相同)
換句話說(shuō),e
把數(shù)字乘以 1
后面跟著給定數(shù)量的 0 的數(shù)字。
1e3 === 1 * 1000; // e3 表示 *1000
1.23e6 === 1.23 * 1000000; // e6 表示 *1000000
現(xiàn)在讓我們寫(xiě)一些非常小的數(shù)字。例如,1 微秒(百萬(wàn)分之一秒):
let mcs = 0.000001;
就像以前一樣,可以使用 "e"
來(lái)完成。如果我們想避免顯式地寫(xiě)零,我們可以這樣寫(xiě):
let mcs = 1e-6; // 1 的左邊有 6 個(gè) 0
如果我們數(shù)一下 0.000001
中的 0 的個(gè)數(shù),是 6 個(gè)。所以自然是 1e-6
。
換句話說(shuō),e
后面的負(fù)數(shù)表示除以 1 后面跟著給定數(shù)量的 0 的數(shù)字:
// -3 除以 1 后面跟著 3 個(gè) 0 的數(shù)字
1e-3 === 1 / 1000; // 0.001
// -6 除以 1 后面跟著 6 個(gè) 0 的數(shù)字
1.23e-6 === 1.23 / 1000000; // 0.00000123
// 一個(gè)更大一點(diǎn)的數(shù)字的示例
1234e-2 === 1234 / 100; // 12.34,小數(shù)點(diǎn)移動(dòng)兩次
十六進(jìn)制 數(shù)字在 JavaScript 中被廣泛用于表示顏色,編碼字符以及其他許多東西。所以自然地,有一種較短的寫(xiě)方法:0x
,然后是數(shù)字。
例如:
alert( 0xff ); // 255
alert( 0xFF ); // 255(一樣,大小寫(xiě)沒(méi)影響)
二進(jìn)制和八進(jìn)制數(shù)字系統(tǒng)很少使用,但也支持使用 0b
和 0o
前綴:
let a = 0b11111111; // 二進(jìn)制形式的 255
let b = 0o377; // 八進(jìn)制形式的 255
alert( a == b ); // true,兩邊是相同的數(shù)字,都是 255
只有這三種進(jìn)制支持這種寫(xiě)法。對(duì)于其他進(jìn)制,我們應(yīng)該使用函數(shù) parseInt
(我們將在本章后面看到)。
方法 num.toString(base)
返回在給定 base
進(jìn)制數(shù)字系統(tǒng)中 num
的字符串表示形式。
舉個(gè)例子:
let num = 255;
alert( num.toString(16) ); // ff
alert( num.toString(2) ); // 11111111
base
的范圍可以從 2
到 36
。默認(rèn)情況下是 10
。
常見(jiàn)的用例如下:
0..9
? 或 ?A..F
?。0
? 或 ?1
?。0..9
? 或 ?A..Z
?。所有拉丁字母都被用于了表示數(shù)字。對(duì)于 ?36
? 進(jìn)制來(lái)說(shuō),一個(gè)有趣且有用的例子是,當(dāng)我們需要將一個(gè)較長(zhǎng)的數(shù)字標(biāo)識(shí)符轉(zhuǎn)換成較短的時(shí)候,例如做一個(gè)短的 URL??梢院?jiǎn)單地使用基數(shù)為 ?36
? 的數(shù)字系統(tǒng)表示:alert( 123456..toString(36) ); // 2n9c
使用兩個(gè)點(diǎn)來(lái)調(diào)用一個(gè)方法
請(qǐng)注意
123456..toString(36)
中的兩個(gè)點(diǎn)不是打錯(cuò)了。如果我們想直接在一個(gè)數(shù)字上調(diào)用一個(gè)方法,比如上面例子中的toString
,那么我們需要在它后面放置兩個(gè)點(diǎn)..
。
如果我們放置一個(gè)點(diǎn):
123456.toString(36)
,那么就會(huì)出現(xiàn)一個(gè) error,因?yàn)?JavaScript 語(yǔ)法隱含了第一個(gè)點(diǎn)之后的部分為小數(shù)部分。如果我們?cè)俜乓粋€(gè)點(diǎn),那么 JavaScript 就知道小數(shù)部分為空,現(xiàn)在使用該方法。
也可以寫(xiě)成
(123456).toString(36)
。
舍入(rounding)是使用數(shù)字時(shí)最常用的操作之一。
這里有幾個(gè)對(duì)數(shù)字進(jìn)行舍入的內(nèi)建函數(shù):
?Math.floor
?
向下舍入:3.1
變成 3
,-1.1
變成 -2
。
?Math.ceil
?
向上舍入:3.1
變成 4
,-1.1
變成 -1
。
?Math.round
?
向最近的整數(shù)舍入:3.1
變成 3
,3.6
變成 4
,中間值 3.5
變成 4
。
?Math.trunc
?(IE 瀏覽器不支持這個(gè)方法)
移除小數(shù)點(diǎn)后的所有內(nèi)容而沒(méi)有舍入:3.1
變成 3
,-1.1
變成 -1
。
這個(gè)是總結(jié)它們之間差異的表格:
Math.floor
|
Math.ceil
|
Math.round
|
Math.trunc
|
|
---|---|---|---|---|
3.1
|
3
|
4
|
3
|
3
|
3.6
|
3
|
4
|
4
|
3
|
-1.1
|
-2
|
-1
|
-1
|
-1
|
-1.6
|
-2
|
-1
|
-2
|
-1
|
這些函數(shù)涵蓋了處理數(shù)字小數(shù)部分的所有可能方法。但是,如果我們想將數(shù)字舍入到小數(shù)點(diǎn)后 n
位,該怎么辦?
例如,我們有 1.2345
,并且想把它舍入到小數(shù)點(diǎn)后兩位,僅得到 1.23
。
有兩種方式可以實(shí)現(xiàn)這個(gè)需求:
例如,要將數(shù)字舍入到小數(shù)點(diǎn)后兩位,我們可以將數(shù)字乘以 100
,調(diào)用舍入函數(shù),然后再將其除回。
let num = 1.23456;
alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
n
位,并以字符串形式返回結(jié)果。let num = 12.34;
alert( num.toFixed(1) ); // "12.3"
這會(huì)向上或向下舍入到最接近的值,類似于 Math.round
:
let num = 12.36;
alert( num.toFixed(1) ); // "12.4"
請(qǐng)注意 toFixed
的結(jié)果是一個(gè)字符串。如果小數(shù)部分比所需要的短,則在結(jié)尾添加零:
let num = 12.34;
alert( num.toFixed(5) ); // "12.34000",在結(jié)尾添加了 0,以達(dá)到小數(shù)點(diǎn)后五位
我們可以使用一元加號(hào)或 Number()
調(diào)用,將其轉(zhuǎn)換為數(shù)字,例如 + num.toFixed(5)
。
在內(nèi)部,數(shù)字是以 64 位格式 IEEE-754 表示的,所以正好有 64 位可以存儲(chǔ)一個(gè)數(shù)字:其中 52 位被用于存儲(chǔ)這些數(shù)字,其中 11 位用于存儲(chǔ)小數(shù)點(diǎn)的位置,而 1 位用于符號(hào)。
如果一個(gè)數(shù)字真的很大,則可能會(huì)溢出 64 位存儲(chǔ),變成一個(gè)特殊的數(shù)值 Infinity
:
alert( 1e500 ); // Infinity
這可能不那么明顯,但經(jīng)常會(huì)發(fā)生的是,精度的損失。
考慮下這個(gè)(falsy!)相等性測(cè)試:
alert( 0.1 + 0.2 == 0.3 ); // false
沒(méi)錯(cuò),如果我們檢查 0.1
和 0.2
的總和是否為 0.3
,我們會(huì)得到 false
。
奇了怪了!如果不是 0.3
,那能是啥?
alert( 0.1 + 0.2 ); // 0.30000000000000004
我擦!想象一下,你創(chuàng)建了一個(gè)電子購(gòu)物網(wǎng)站,如果訪問(wèn)者將價(jià)格為 ¥ 0.10
和 ¥ 0.20
的商品放入了他的購(gòu)物車。訂單總額將是 ¥ 0.30000000000000004
。這會(huì)讓任何人感到驚訝。
但為什么會(huì)這樣呢?
一個(gè)數(shù)字以其二進(jìn)制的形式存儲(chǔ)在內(nèi)存中,一個(gè) 1 和 0 的序列。但是在十進(jìn)制數(shù)字系統(tǒng)中看起來(lái)很簡(jiǎn)單的 0.1
,0.2
這樣的小數(shù),實(shí)際上在二進(jìn)制形式中是無(wú)限循環(huán)小數(shù)。
什么是 0.1
?0.1
就是 1
除以 10
,1/10
,即十分之一。在十進(jìn)制數(shù)字系統(tǒng)中,這樣的數(shù)字表示起來(lái)很容易。將其與三分之一進(jìn)行比較:1/3
。三分之一變成了無(wú)限循環(huán)小數(shù) 0.33333(3)
。
在十進(jìn)制數(shù)字系統(tǒng)中,可以保證以 10
的整數(shù)次冪作為除數(shù)能夠正常工作,但是以 3
作為除數(shù)則不能。也是同樣的原因,在二進(jìn)制數(shù)字系統(tǒng)中,可以保證以 2
的整數(shù)次冪作為除數(shù)時(shí)能夠正常工作,但 1/10
就變成了一個(gè)無(wú)限循環(huán)的二進(jìn)制小數(shù)。
使用二進(jìn)制數(shù)字系統(tǒng)無(wú)法 精確 存儲(chǔ) 0.1 或 0.2,就像沒(méi)有辦法將三分之一存儲(chǔ)為十進(jìn)制小數(shù)一樣。
IEEE-754 數(shù)字格式通過(guò)將數(shù)字舍入到最接近的可能數(shù)字來(lái)解決此問(wèn)題。這些舍入規(guī)則通常不允許我們看到“極小的精度損失”,但是它確實(shí)存在。
我們可以看到:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
當(dāng)我們對(duì)兩個(gè)數(shù)字進(jìn)行求和時(shí),它們的“精度損失”會(huì)疊加起來(lái)。
這就是為什么 0.1 + 0.2
不等于 0.3
。
不僅僅是 JavaScript
許多其他編程語(yǔ)言也存在同樣的問(wèn)題。
PHP,Java,C,Perl,Ruby 給出的也是完全相同的結(jié)果,因?yàn)樗鼈兓诘氖窍嗤臄?shù)字格式。
我們能解決這個(gè)問(wèn)題嗎?當(dāng)然,最可靠的方法是借助方法 toFixed(n) 對(duì)結(jié)果進(jìn)行舍入:
let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // 0.30
請(qǐng)注意,toFixed
總是返回一個(gè)字符串。它確保小數(shù)點(diǎn)后有 2 位數(shù)字。如果我們有一個(gè)電子購(gòu)物網(wǎng)站,并需要顯示 ¥ 0.30
,這實(shí)際上很方便。對(duì)于其他情況,我們可以使用一元加號(hào)將其強(qiáng)制轉(zhuǎn)換為一個(gè)數(shù)字:
let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // "0.30"
我們可以將數(shù)字臨時(shí)乘以 100(或更大的數(shù)字),將其轉(zhuǎn)換為整數(shù),進(jìn)行數(shù)學(xué)運(yùn)算,然后再除回。當(dāng)我們使用整數(shù)進(jìn)行數(shù)學(xué)運(yùn)算時(shí),誤差會(huì)有所減少,但仍然可以在除法中得到:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
因此,乘/除法可以減少誤差,但不能完全消除誤差。
有時(shí)候我們可以嘗試完全避免小數(shù)。例如,我們正在創(chuàng)建一個(gè)電子購(gòu)物網(wǎng)站,那么我們可以用角而不是元來(lái)存儲(chǔ)價(jià)格。但是,如果我們要打 30% 的折扣呢?實(shí)際上,完全避免小數(shù)處理幾乎是不可能的。只需要在必要時(shí)剪掉其“尾巴”來(lái)對(duì)其進(jìn)行舍入即可。
有趣的事兒
嘗試運(yùn)行下面這段代碼:
// Hello!我是一個(gè)會(huì)自我增加的數(shù)字! alert( 9999999999999999 ); // 顯示 10000000000000000
出現(xiàn)了同樣的問(wèn)題:精度損失。有 64 位來(lái)表示該數(shù)字,其中 52 位可用于存儲(chǔ)數(shù)字,但這還不夠。所以最不重要的數(shù)字就消失了。
JavaScript 不會(huì)在此類事件中觸發(fā) error。它會(huì)盡最大努力使數(shù)字符合所需的格式,但不幸的是,這種格式不夠大到滿足需求。
兩個(gè)零
數(shù)字內(nèi)部表示的另一個(gè)有趣結(jié)果是存在兩個(gè)零:
0
和-0
。
這是因?yàn)樵诖鎯?chǔ)時(shí),使用一位來(lái)存儲(chǔ)符號(hào),因此對(duì)于包括零在內(nèi)的任何數(shù)字,可以設(shè)置這一位或者不設(shè)置。
在大多數(shù)情況下,這種區(qū)別并不明顯,因?yàn)檫\(yùn)算符將它們視為相同的值。
還記得這兩個(gè)特殊的數(shù)值嗎?
Infinity
?(和 ?-Infinity
?)是一個(gè)特殊的數(shù)值,比任何數(shù)值都大(?。?。NaN
?代表一個(gè) error。它們屬于 ?number
?類型,但不是“普通”數(shù)字,因此,這里有用于檢查它們的特殊函數(shù):
isNaN(value)
? 將其參數(shù)轉(zhuǎn)換為數(shù)字,然后測(cè)試它是否為 ?NaN
?:alert( isNaN(NaN) ); // true
alert( isNaN("str") ); // true
但是我們需要這個(gè)函數(shù)嗎?我們不能只使用 === NaN
比較嗎?很不幸,這不行。值 “NaN” 是獨(dú)一無(wú)二的,它不等于任何東西,包括它自身:
alert( NaN === NaN ); // false
isFinite(value)
? 將其參數(shù)轉(zhuǎn)換為數(shù)字,如果是常規(guī)數(shù)字而不是 ?NaN/Infinity/-Infinity
?,則返回 ?true
?:alert( isFinite("15") ); // true
alert( isFinite("str") ); // false,因?yàn)槭且粋€(gè)特殊的值:NaN
alert( isFinite(Infinity) ); // false,因?yàn)槭且粋€(gè)特殊的值:Infinity
有時(shí) isFinite
被用于驗(yàn)證字符串值是否為常規(guī)數(shù)字:
let num = +prompt("Enter a number", '');
// 結(jié)果會(huì)是 true,除非你輸入的是 Infinity、-Infinity 或不是數(shù)字
alert( isFinite(num) );
請(qǐng)注意,在所有數(shù)字函數(shù)中,包括 isFinite
,空字符串或僅有空格的字符串均被視為 0
。
與 ?
Object.is
? 進(jìn)行比較有一個(gè)特殊的內(nèi)建方法
Object.is
,它類似于===
一樣對(duì)值進(jìn)行比較,但它對(duì)于兩種邊緣情況更可靠:
- 它適用于 ?
NaN
?:?Object.is(NaN, NaN) === true
?,這是件好事。- 值 ?
0
? 和 ?-0
? 是不同的:?Object.is(0, -0) === false
?,從技術(shù)上講這是對(duì)的,因?yàn)樵趦?nèi)部,數(shù)字的符號(hào)位可能會(huì)不同,即使其他所有位均為零。在所有其他情況下,
Object.is(a, b)
與a === b
相同。
這種比較方式經(jīng)常被用在 JavaScript 規(guī)范中。當(dāng)內(nèi)部算法需要比較兩個(gè)值是否完全相同時(shí),它使用
Object.is
(內(nèi)部稱為 SameValue)。
使用加號(hào) +
或 Number()
的數(shù)字轉(zhuǎn)換是嚴(yán)格的。如果一個(gè)值不完全是一個(gè)數(shù)字,就會(huì)失?。?
alert( +"100px" ); // NaN
唯一的例外是字符串開(kāi)頭或結(jié)尾的空格,因?yàn)樗鼈儠?huì)被忽略。
但在現(xiàn)實(shí)生活中,我們經(jīng)常會(huì)有帶有單位的值,例如 CSS 中的 "100px"
或 "12pt"
。并且,在很多國(guó)家,貨幣符號(hào)是緊隨金額之后的,所以我們有 "19€"
,并希望從中提取出一個(gè)數(shù)值。
這就是 parseInt
和 parseFloat
的作用。
它們可以從字符串中“讀取”數(shù)字,直到無(wú)法讀取為止。如果發(fā)生 error,則返回收集到的數(shù)字。函數(shù) parseInt
返回一個(gè)整數(shù),而 parseFloat
返回一個(gè)浮點(diǎn)數(shù):
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5
alert( parseInt('12.3') ); // 12,只有整數(shù)部分被返回了
alert( parseFloat('12.3.4') ); // 12.3,在第二個(gè)點(diǎn)出停止了讀取
某些情況下,parseInt/parseFloat
會(huì)返回 NaN
。當(dāng)沒(méi)有數(shù)字可讀時(shí)會(huì)發(fā)生這種情況:
alert( parseInt('a123') ); // NaN,第一個(gè)符號(hào)停止了讀取
parseInt(str, radix) 的第二個(gè)參數(shù)
parseInt()
函數(shù)具有可選的第二個(gè)參數(shù)。它指定了數(shù)字系統(tǒng)的基數(shù),因此parseInt
還可以解析十六進(jìn)制數(shù)字、二進(jìn)制數(shù)字等的字符串:
alert( parseInt('0xff', 16) ); // 255 alert( parseInt('ff', 16) ); // 255,沒(méi)有 0x 仍然有效 alert( parseInt('2n9c', 36) ); // 123456
JavaScript 有一個(gè)內(nèi)建的 Math 對(duì)象,它包含了一個(gè)小型的數(shù)學(xué)函數(shù)和常量庫(kù)。
幾個(gè)例子:
?Math.random()
?
返回一個(gè)從 0 到 1 的隨機(jī)數(shù)(不包括 1)。
alert( Math.random() ); // 0.1234567894322
alert( Math.random() ); // 0.5435252343232
alert( Math.random() ); // ... (任何隨機(jī)數(shù))
Math.max(a, b, c...)
和 Math.min(a, b, c...)
從任意數(shù)量的參數(shù)中返回最大值和最小值。
alert( Math.max(3, 5, -10, 0, 1) ); // 5
alert( Math.min(1, 2) ); // 1
?Math.pow(n, power)
?
返回 n
的給定(power)次冪。
alert( Math.pow(2, 10) ); // 2 的 10 次冪 = 1024
Math
對(duì)象中還有更多函數(shù)和常量,包括三角函數(shù),你可以在 Math 對(duì)象文檔 中找到這些內(nèi)容。
要寫(xiě)有很多零的數(shù)字:
"e"
? 和 0 的數(shù)量附加到數(shù)字后。就像:?123e6
?與 ?123
?后面接 6 個(gè) 0 相同。"e"
? 后面的負(fù)數(shù)將使數(shù)字除以 1 后面接著給定數(shù)量的零的數(shù)字。例如 ?123e-6
? 表示 ?0.000123
?(?123
?的百萬(wàn)分之一)。對(duì)于不同的數(shù)字系統(tǒng):
0x
?),八進(jìn)制(?0o
?)和二進(jìn)制(?0b
?)系統(tǒng)中寫(xiě)入數(shù)字。parseInt(str, base)
? 將字符串 ?str
?解析為在給定的 ?base
?數(shù)字系統(tǒng)中的整數(shù),?2 ≤ base ≤ 36
?。num.toString(base)
? 將數(shù)字轉(zhuǎn)換為在給定的 ?base
?數(shù)字系統(tǒng)中的字符串。對(duì)于常規(guī)數(shù)字檢測(cè):
isNaN(value)
? 將其參數(shù)轉(zhuǎn)換為數(shù)字,然后檢測(cè)它是否為 ?NaN
?isFinite(value)
? 將其參數(shù)轉(zhuǎn)換為數(shù)字,如果它是常規(guī)數(shù)字,則返回 ?true
?,而不是 ?NaN/Infinity/-Infinity
?要將 ?12pt
? 和 ?100px
? 之類的值轉(zhuǎn)換為數(shù)字:
parseInt/parseFloat
? 進(jìn)行“軟”轉(zhuǎn)換,它從字符串中讀取數(shù)字,然后返回在發(fā)生 error 前可以讀取到的值。小數(shù):
Math.floor
?,?Math.ceil
?,?Math.trunc
?,?Math.round
? 或 ?num.toFixed(precision)
? 進(jìn)行舍入。更多數(shù)學(xué)函數(shù):
重要程度: 5
創(chuàng)建一個(gè)腳本,提示訪問(wèn)者輸入兩個(gè)數(shù)字,然后顯示它們的總和。
P.S. 有一個(gè)類型陷阱。
let a = +prompt("The first number?", "");
let b = +prompt("The second number?", "");
alert( a + b );
注意在 prompt
前面的一元加號(hào) +
。它將立即把值轉(zhuǎn)換成數(shù)字。
否則,a
和 b
將會(huì)是字符串,它們的總和將是它們的連接,即:"1" + "2" = "12"
。
重要程度: 4
根據(jù)文檔,Math.round
和 toFixed
都將數(shù)字舍入到最接近的數(shù)字:0..4
會(huì)被舍去,而 5..9
會(huì)進(jìn)一位。
例如:
alert( 1.35.toFixed(1) ); // 1.4
在下面這個(gè)類似的示例中,為什么 6.35
被舍入為 6.3
而不是 6.4
?
alert( 6.35.toFixed(1) ); // 6.3
如何以正確的方式來(lái)對(duì) 6.35
進(jìn)行舍入?
在內(nèi)部,6.35
的小數(shù)部分是一個(gè)無(wú)限的二進(jìn)制。在這種情況下,它的存儲(chǔ)會(huì)造成精度損失。
讓我們來(lái)看看:
alert( 6.35.toFixed(20) ); // 6.34999999999999964473
精度損失可能會(huì)導(dǎo)致數(shù)字的增加和減小。在這種特殊的情況下,數(shù)字變小了一點(diǎn),這就是它向下舍入的原因。
那么 1.35
會(huì)怎樣呢?
alert( 1.35.toFixed(20) ); // 1.35000000000000008882
在這里,精度損失使得這個(gè)數(shù)字稍微大了一些,因此其向上舍入。
如果我們希望以正確的方式進(jìn)行舍入,我們應(yīng)該如何解決 6.35
的舍入問(wèn)題呢?
在進(jìn)行舍入前,我們應(yīng)該使其更接近整數(shù):
alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000
請(qǐng)注意,63.5
完全沒(méi)有精度損失。這是因?yàn)樾?shù)部分 0.5
實(shí)際上是 1/2
。以 2 的整數(shù)次冪為分母的小數(shù)在二進(jìn)制數(shù)字系統(tǒng)中可以被精確地表示,現(xiàn)在我們可以對(duì)它進(jìn)行舍入:
alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4
重要程度: 5
創(chuàng)建一個(gè)函數(shù) readNumber
,它提示輸入一個(gè)數(shù)字,直到訪問(wèn)者輸入一個(gè)有效的數(shù)字為止。
結(jié)果值必須以數(shù)字形式返回。
訪問(wèn)者也可以通過(guò)輸入空行或點(diǎn)擊“取消”來(lái)停止該過(guò)程。在這種情況下,函數(shù)應(yīng)該返回 null
。
function readNumber() {
let num;
do {
num = prompt("Enter a number please?", 0);
} while ( !isFinite(num) );
if (num === null || num === '') return null;
return +num;
}
alert(`Read: ${readNumber()}`);
該解決方案有點(diǎn)復(fù)雜,因?yàn)槲覀冃枰幚?nbsp;null
和空行。
所以,我們實(shí)際上接受輸入,直到輸入的是一個(gè)“常規(guī)數(shù)字”。null
(取消)和空行都符合該條件,因?yàn)樵跀?shù)字形式中它們是 0
。
在我們停止之后,我們需要專門(mén)處理 null
和空行(返回 null
),因?yàn)閷⑺鼈冝D(zhuǎn)換為數(shù)字將返回 0
。
重要程度: 4
這是一個(gè)無(wú)限循環(huán)。它永遠(yuǎn)不會(huì)結(jié)束。為什么?
let i = 0;
while (i != 10) {
i += 0.2;
}
那是因?yàn)?nbsp;i
永遠(yuǎn)不會(huì)等于 10
。
運(yùn)行下面這段代碼來(lái)查看 i
的 實(shí)際 值:
let i = 0;
while (i < 11) {
i += 0.2;
if (i > 9.8 && i < 10.2) alert( i );
}
它們中沒(méi)有一個(gè)恰好是 10
。
之所以發(fā)生這種情況,是因?yàn)閷?duì) 0.2
這樣的小數(shù)時(shí)進(jìn)行加法運(yùn)算時(shí)出現(xiàn)了精度損失。
結(jié)論:在處理小數(shù)時(shí)避免相等性檢查。
重要程度: 2
內(nèi)建函數(shù) Math.random()
會(huì)創(chuàng)建一個(gè)在 0
到 1
之間(不包括 1
)的隨機(jī)數(shù)。
編寫(xiě)一個(gè) random(min, max)
函數(shù),用以生成一個(gè)在 min
到 max
之間的隨機(jī)浮點(diǎn)數(shù)(不包括 max
))。
運(yùn)行示例:
alert( random(1, 5) ); // 1.2345623452
alert( random(1, 5) ); // 3.7894332423
alert( random(1, 5) ); // 4.3435234525
我們需要將區(qū)間 0…1 中的所有值“映射”為范圍在 min
到 max
中的值。
這可以分兩個(gè)階段完成:
max-min
?,則隨機(jī)數(shù)的范圍將從 0…1 增加到 ?0..max-min
?。min
?相加,則隨機(jī)數(shù)的范圍將為 ?min
?到 ?max
?。函數(shù)實(shí)現(xiàn):
function random(min, max) {
return min + Math.random() * (max - min);
}
alert( random(1, 5) );
alert( random(1, 5) );
alert( random(1, 5) );
重要程度: 2
創(chuàng)建一個(gè)函數(shù) randomInteger(min, max)
,該函數(shù)會(huì)生成一個(gè)范圍在 min
到 max
中的隨機(jī)整數(shù),包括 min
和 max
。
在 min..max
范圍中的所有數(shù)字的出現(xiàn)概率必須相同。
運(yùn)行示例:
alert( randomInteger(1, 5) ); // 1
alert( randomInteger(1, 5) ); // 3
alert( randomInteger(1, 5) ); // 5
你可以使用 上一個(gè)任務(wù) 的解決方案作為基礎(chǔ)。
簡(jiǎn)單但錯(cuò)誤的解決方案
最簡(jiǎn)單但錯(cuò)誤的解決方案是生成一個(gè)范圍在
min
到max
的值,并取對(duì)其進(jìn)行四舍五入后的值:
function randomInteger(min, max) { let rand = min + Math.random() * (max - min); return Math.round(rand); } alert( randomInteger(1, 3) );
這個(gè)函數(shù)是能起作用的,但不正確。獲得邊緣值
min
和max
的概率比其他值低兩倍。
如果你將上面這個(gè)例子運(yùn)行多次,你會(huì)很容易看到
2
出現(xiàn)的頻率最高。
發(fā)生這種情況是因?yàn)?nbsp;
Math.round()
從范圍1..3
中獲得隨機(jī)數(shù),并按如下所示進(jìn)行四舍五入:
values from 1 ... to 1.4999999999 become 1 values from 1.5 ... to 2.4999999999 become 2 values from 2.5 ... to 2.9999999999 become 3
現(xiàn)在我們可以清楚地看到
1
的值比2
少兩倍。和3
一樣。
正確的解決方案
這個(gè)題目有很多正確的解決方案。其中之一是調(diào)整取值范圍的邊界。為了確保相同的取值范圍,我們可以生成從 0.5 到 3.5 的值,從而將所需的概率添加到取值范圍的邊界:
function randomInteger(min, max) { // 現(xiàn)在范圍是從 (min-0.5) 到 (max+0.5) let rand = min - 0.5 + Math.random() * (max - min + 1); return Math.round(rand); } alert( randomInteger(1, 3) );
另一種方法是使用
Math.floor
來(lái)取范圍從min
到max+1
的隨機(jī)數(shù):
function randomInteger(min, max) { // here rand is from min to (max+1) let rand = min + Math.random() * (max + 1 - min); return Math.floor(rand); } alert( randomInteger(1, 3) );
現(xiàn)在所有間隔都以這種方式映射:
values from 1 ... to 1.9999999999 become 1 values from 2 ... to 2.9999999999 become 2 values from 3 ... to 3.9999999999 become 3
所有間隔的長(zhǎng)度相同,從而使最終能夠均勻分配。
更多建議: