Javascript 數(shù)字類型

2023-02-17 10:44 更新

在現(xiàn)代 JavaScript 中,數(shù)字(number)有兩種類型:

  1. JavaScript 中的常規(guī)數(shù)字以 64 位的格式 IEEE-754 存儲(chǔ),也被稱為“雙精度浮點(diǎn)數(shù)”。這是我們大多數(shù)時(shí)候所使用的數(shù)字,我們將在本章中學(xué)習(xí)它們。
  2. BigInt 用于表示任意長(zhǎng)度的整數(shù)。有時(shí)會(huì)需要它們,因?yàn)檎缥覀冊(cè)谇懊娴恼鹿?jié) 數(shù)據(jù)類型 中提到的,常規(guī)整數(shù)不能安全地超過(guò) ?(253-1)? 或小于 ?-(253-1)?。由于僅在少數(shù)特殊領(lǐng)域才會(huì)用到  BigInt,因此我們?cè)谔厥獾恼鹿?jié) BigInt 中對(duì)其進(jìn)行了介紹。

所以,在這里我們將討論常規(guī)數(shù)字類型?,F(xiàn)在讓我們開(kāi)始學(xué)習(xí)吧。

編寫(xiě)數(shù)字的更多方法

假如我們需要表示 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)制,二進(jìn)制和八進(jìn)制數(shù)字

十六進(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(我們將在本章后面看到)。

toString(base)

方法 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)的用例如下:

  • base=16 用于十六進(jìn)制顏色,字符編碼等,數(shù)字可以是 ?0..9? 或 ?A..F?。
  • base=2 主要用于調(diào)試按位操作,數(shù)字可以是 ?0? 或 ?1?。
  • base=36 是最大進(jìn)制,數(shù)字可以是 ?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 變成 33.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è)需求:

  1. 乘除法
  2. 例如,要將數(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
  3. 函數(shù) toFixed(n) 將數(shù)字舍入到小數(shù)點(diǎn)后 n 位,并以字符串形式返回結(jié)果。
  4. 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)。

不精確的計(jì)算

在內(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)算符將它們視為相同的值。

測(cè)試:isFinite 和 isNaN

還記得這兩個(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ì)于兩種邊緣情況更可靠:

  1. 它適用于 ?NaN?:?Object.is(NaN, NaN) === true?,這是件好事。
  2. 值 ?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)。

parseInt 和 parseFloat

使用加號(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

其他數(shù)學(xué)函數(shù)

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)容。

總結(jié)

要寫(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):

  • 可以直接在十六進(jìn)制(?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)行舍入。
  • 請(qǐng)確保記住使用小數(shù)時(shí)會(huì)損失精度。

更多數(shù)學(xué)函數(shù):

  • 需要時(shí)請(qǐng)查看 Math 對(duì)象。這個(gè)庫(kù)很小,但是可以滿足基本的需求。

任務(wù)


來(lái)自訪問(wèn)者的數(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"。


為什么 6.35.toFixed(1) == 6.3?

重要程度: 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

重復(fù),直到輸入的是一個(gè)數(shù)字

重要程度: 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。


一個(gè)偶發(fā)的無(wú)限循環(huán)

重要程度: 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í)避免相等性檢查。


從 min 到 max 的隨機(jī)數(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è)階段完成:

  1. 如果我們將 0…1 的隨機(jī)數(shù)乘以 ?max-min?,則隨機(jī)數(shù)的范圍將從 0…1 增加到 ?0..max-min?。
  2. 現(xiàn)在,如果我們將隨機(jī)數(shù)與 ?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) );

從 min 到 max 的隨機(jī)整數(shù)

重要程度: 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)度相同,從而使最終能夠均勻分配。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)