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