5. 新的數字和數學功能
目錄
請支持這本書:購買它(PDF、EPUB、MOBI)捐款
(廣告,請不要封鎖。)

5. 新的數字和Math功能



5.1 概觀

5.1.1 新的整數文字

您現在可以使用二進位和八進位表示法指定整數

> 0xFF // ES5: hexadecimal
255
> 0b11 // ES6: binary
3
> 0o10 // ES6: octal
8

5.1.2 新的Number屬性

全域物件Number獲得了一些新的屬性

5.1.3 新的Math方法

全域物件 Math 有新的方法,用於數值、三角函數和位元運算。我們來看四個範例。

Math.sign() 傳回數字的符號

> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1

Math.trunc() 移掉數字的小數部分

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

Math.log10() 計算以 10 為底的對數

> Math.log10(100)
2

Math.hypot() 計算其參數平方和的平方根(畢氏定理)

> Math.hypot(3, 4)
5    

5.2 新的整數文字

ECMAScript 5 已經有十六進位整數文字

> 0x9
9
> 0xA
10
> 0x10
16
> 0xFF
255

ECMAScript 6 帶來兩種新的整數文字

請記住,Number 方法 toString(radix) 可用於查看非 10 進位的數字

> 255..toString(16)
'ff'
> 4..toString(2)
'100'
> 8..toString(8)
'10'

(需要雙點,這樣存取屬性的點才不會和十進位點搞混。)

5.2.1 八進位文字的用例:Unix 風格的檔案權限

在 Node.js 檔案系統模組中,幾個函數有參數 mode。它的值用於指定檔案權限,透過一種從 Unix 遺留下來的編碼

這表示權限可以用 9 個位元表示(3 個類別,每個類別有 3 個權限)

  使用者 群組 所有人
權限 r, w, x r, w, x r, w, x
位元 8, 7, 6 5, 4, 3 2, 1, 0

單一類別使用者的權限儲存在 3 個位元中

位元 權限 八進位數字
000 ––– 0
001 ––x 1
010 –w– 2
011 –wx 3
100 r–– 4
101 r–x 5
110 rw– 6
111 rwx 7

這表示八進制數字是所有權限的緊湊表示,您只需要 3 個數字,每個使用者類別一個數字。兩個範例

5.2.2 Number.parseInt() 和新的整數文字

Number.parseInt()(執行與全域函式 parseInt() 相同的動作)具有下列簽章

Number.parseInt(string, radix?)
5.2.2.1 Number.parseInt():十六進位數字文字

Number.parseInt() 提供對十六進位文字符表示法的特殊支援,如果

例如

> Number.parseInt('0xFF')
255
> Number.parseInt('0xFF', 0)
255
> Number.parseInt('0xFF', 16)
255

在所有其他情況下,只會分析數字直到第一個非數字

> Number.parseInt('0xFF', 10)
0
> Number.parseInt('0xFF', 17)
0
5.2.2.2 Number.parseInt():二進位和八進位數字文字

不過,Number.parseInt() 沒有對二進位或八進位文字提供特殊支援!

> Number.parseInt('0b111')
0
> Number.parseInt('0b111', 2)
0
> Number.parseInt('111', 2)
7

> Number.parseInt('0o10')
0
> Number.parseInt('0o10', 8)
0
> Number.parseInt('10', 8)
8

如果您想要分析這些類型的文字,您需要使用 Number()

> Number('0b111')
7
> Number('0o10')
8

Number.parseInt() 對於具有不同基底的數字運作良好,只要沒有特殊字首,且有提供參數 radix

> Number.parseInt('111', 2)
7
> Number.parseInt('10', 8)
8

5.3 新的靜態 Number 屬性

本節說明建構函式 Number 在 ECMAScript 6 中新增的屬性。

5.3.1 以前的全域函式

四個與數字相關的函式已作為全域函式提供,並已新增至 Number 作為方法:isFiniteisNaNparseFloatparseInt。它們的運作方式幾乎與其全域對應函式相同,但 isFiniteisNaN 不再強制其引數為數字,這對 isNaN 來說特別重要。下列小節說明所有詳細資料。

5.3.1.1 Number.isFinite(number)

Number.isFinite(number) 判斷 number 是否為實際數字(既不是 Infinity 也不是 -Infinity 也不是 NaN

> Number.isFinite(Infinity)
false
> Number.isFinite(-Infinity)
false
> Number.isFinite(NaN)
false
> Number.isFinite(123)
true

此方法的優點是它不會強制參數為數字(而全域函數會)

> Number.isFinite('123')
false
> isFinite('123')
true
5.3.1.2 Number.isNaN(number)

Number.isNaN(number) 檢查 number 是否為 NaN 值。

ES5 進行此檢查的一種方法是透過 !==

> const x = NaN;
> x !== x
true

更具描述性的方法是透過全域函數 isNaN()

> const x = NaN;
> isNaN(x)
true

不過,此函數會強制非數字轉換為數字,如果結果為 NaN 則傳回 true(通常不是您要的結果)

> isNaN('???')
true

新方法 Number.isNaN() 沒有這個問題,因為它不會強制參數轉換為數字

> Number.isNaN('???')
false
5.3.1.3 Number.parseFloatNumber.parseInt

以下兩個方法的運作方式與同名的全域函數完全相同。它們新增至 Number 是為了完整性;現在所有與數字相關的函數都可以在這裡找到。

5.3.2 Number.EPSILON

特別是對於小數,四捨五入誤差可能會成為 JavaScript 中的問題3。例如,0.1 和 0.2 無法精確表示,如果您將它們加起來並與 0.3 比較(也無法精確表示),就會發現這一點。

> 0.1 + 0.2 === 0.3
false

Number.EPSILON 在比較浮點數時指定合理的誤差範圍。它提供了一種比較浮點數值的更好方法,如下列函數所示。

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

5.3.3 Number.isInteger(number)

JavaScript 只有浮點數(雙精度)。因此,整數只是沒有小數部分的浮點數。

Number.isInteger(number) 如果 number 是數字且沒有小數部分,則傳回 true

> Number.isInteger(-17)
true
> Number.isInteger(33)
true
> Number.isInteger(33.1)
false
> Number.isInteger('33')
false
> Number.isInteger(NaN)
false
> Number.isInteger(Infinity)
false

5.3.4 安全整數

JavaScript 數字只有足夠的儲存空間來表示 53 位元有號整數。也就是說,範圍為 −253 < i < 253 的整數 i安全的。其具體含義將在稍後說明。下列屬性有助於判斷 JavaScript 整數是否安全

安全整數 的概念集中在數學整數在 JavaScript 中的表示方式上。在範圍 (−253, 253)(不包括下限和上限)中,JavaScript 整數是 安全的:它們與其表示的數學整數之間存在一對一的對應關係。

超出此範圍,JavaScript 整數是不 安全的:兩個或多個數學整數表示為同一個 JavaScript 整數。例如,從 253 開始,JavaScript 只能表示每個數學整數的第二個

> Math.pow(2, 53)
9007199254740992

> 9007199254740992
9007199254740992
> 9007199254740993
9007199254740992
> 9007199254740994
9007199254740994
> 9007199254740995
9007199254740996
> 9007199254740996
9007199254740996
> 9007199254740997
9007199254740996

因此,安全的 JavaScript 整數是一個明確表示單一數學整數的整數。

指定安全整數下限和上限的兩個靜態 Number 屬性可定義如下

Number.MAX_SAFE_INTEGER = Math.pow(2, 53)-1;
Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;

Number.isSafeInteger() 判斷 JavaScript 數字是否為安全整數,可定義如下

Number.isSafeInteger = function (n) {
    return (typeof n === 'number' &&
        Math.round(n) === n &&
        Number.MIN_SAFE_INTEGER <= n &&
        n <= Number.MAX_SAFE_INTEGER);
}

對於給定的值 n,此函數首先檢查 n 是否為數字和整數。如果兩個檢查都成功,則 n 大於或等於 MIN_SAFE_INTEGER 且小於或等於 MAX_SAFE_INTEGER,則 n 是安全的。

5.3.4.2 何時使用整數進行運算才正確?

我們如何確保使用整數進行運算的結果是正確的?例如,下列結果明顯不正確

> 9007199254740990 + 3
9007199254740992

我們有兩個安全的運算元,但結果不安全

> Number.isSafeInteger(9007199254740990)
true
> Number.isSafeInteger(3)
true
> Number.isSafeInteger(9007199254740992)
false

下列結果也不正確

> 9007199254740995 - 10
9007199254740986

這次,結果是安全的,但其中一個運算元不是

> Number.isSafeInteger(9007199254740995)
false
> Number.isSafeInteger(10)
true
> Number.isSafeInteger(9007199254740986)
true

因此,只有當所有運算元和結果都是安全的,應用整數運算子 op 的結果才保證正確。更正式地說

isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)

暗示 a op b 是正確的結果。

5.4 新的 Math 功能

在 ECMAScript 6 中,全域物件 Math 有好幾個新方法。

5.4.1 各種數值功能

5.4.1.1 Math.sign(x)

Math.sign(x) 回傳

範例

> Math.sign(-8)
-1
> Math.sign(3)
1

> Math.sign(0)
0
> Math.sign(NaN)
NaN

> Math.sign(-Infinity)
-1
> Math.sign(Infinity)
1
5.4.1.2 Math.trunc(x)

Math.trunc(x) 移除了 x 的小數部分。補充其他捨入方法 Math.floor()Math.ceil()Math.round()

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

你可以像這樣實作 Math.trunc()

function trunc(x) {
    return Math.sign(x) * Math.floor(Math.abs(x));
}
5.4.1.3 Math.cbrt(x)

Math.cbrt(x) 回傳 x 的立方根(∛x)。

> Math.cbrt(8)
2

5.4.2 在指數和對數中使用 0 代替 1

如果小數出現在零之後,可以更精確地表示。我將使用小數來示範(JavaScript 的數字在內部以 2 為底儲存,但相同的推理適用)。

以 10 為底的浮點數在內部表示為 尾數 × 10指數尾數 在小數點之前有一個位數,而指數會根據需要「移動」小數點。這表示如果你將小數轉換為內部表示,小數點之前的一個零會導致比小數點之前的一個一更小的尾數。例如

就精確度而言,這裡重要的數量是尾數的容量,以有效位數衡量。這就是為什麼 (A) 比 (B) 給你更高的精確度。

此外,JavaScript 會以更高的精度表示接近零的數字(例如小數)。

5.4.2.1 Math.expm1(x)

Math.expm1(x) 傳回 Math.exp(x)-1Math.log1p() 的反函數。

因此,只要 Math.exp() 的結果接近 1,這個方法就會提供更高的精度。您可以在以下互動中看到兩者之間的差異

> Math.expm1(1e-10)
1.00000000005e-10
> Math.exp(1e-10)-1
1.000000082740371e-10

前者是更好的結果,您可以使用一個函式庫(例如 decimal.js)來驗證,該函式庫提供具有任意精度的浮點數(「bigfloat」)

> var Decimal = require('decimal.js').config({precision:50});
> new Decimal(1e-10).exp().minus(1).toString()
'1.000000000050000000001666666666708333333e-10'
5.4.2.2 Math.log1p(x)

Math.log1p(x) 傳回 Math.log(1 + x)Math.expm1() 的反函數。

因此,這個方法讓您可以使用更高的精度指定接近 1 的參數。以下範例說明了原因。

以下兩個 log() 呼叫會產生相同的結果

> Math.log(1 + 1e-16)
0
> Math.log(1 + 0)
0

相反地,log1p() 會產生不同的結果

> Math.log1p(1e-16)
1e-16
> Math.log1p(0)
0

Math.log1p() 精度較高的原因是 1 + 1e-16 的正確結果比 1e-16 有更多有效數字

> 1 + 1e-16 === 1
true
> 1e-16 === 0
false

5.4.3 以 2 和 10 為底數的對數

5.4.3.1 Math.log2(x)

Math.log2(x) 計算以 2 為底數的對數。

> Math.log2(8)
3
5.4.3.2 Math.log10(x)

Math.log10(x) 計算以 10 為底數的對數。

> Math.log10(100)
2

5.4.4 支援編譯成 JavaScript

Emscripten 開創了一種編碼風格,後來被 asm.js 採用:虛擬機器(想想位元組碼)的操作以 JavaScript 的靜態子集表示。JavaScript 引擎可以有效地執行該子集:如果它是從 C++ 編譯的結果,則執行速度約為原生速度的 70%。

以下 Math 方法主要是為了支援 asm.js 和類似的編譯策略而新增的,它們對其他應用程式沒有那麼有用。

5.4.4.1 Math.fround(x)

Math.fround(x)x 四捨五入為 32 位元浮點值 (float)。asm.js 使用它來告知引擎在內部使用 float 值。

5.4.4.2 Math.imul(x, y)

Math.imul(x, y) 將兩個 32 位元整數 xy 相乘,並傳回結果的低 32 位元。這是唯一無法透過使用 JavaScript 運算子並將結果強制轉換回 32 位元的 32 位元基本數學運算。例如,idiv 可實作如下

function idiv(x, y) {
    return (x / y) | 0;
}

相反地,將兩個大型 32 位元整數相乘可能會產生一個非常大的雙精度浮點數,以致於低位元會遺失。

5.4.5 位元運算

為什麼這很有趣?引用 Miro Samek 的「快速、確定性且可攜式計算開頭零

計算整數中的開頭零在許多 DSP 演算法中都是一項重要的運算,例如音訊或視訊處理中範例的正規化,以及在即時排程器中快速找出優先順序最高的準備執行任務。

5.4.6 三角函數方法

5.5 常見問題:數字

5.5.1 我如何使用超過 JavaScript 53 位元範圍的整數?

JavaScript 整數的範圍為 53 位元。當需要 64 位元整數時,這會造成問題。例如:在它的 JSON API 中,當推文 ID 變得太大時,Twitter 必須從整數切換為字串。

目前,解決此限制的唯一方法是使用高精度數字(大整數或大浮點數)的函式庫。其中一個函式庫是 decimal.js

支援 JavaScript 中較大整數的計畫已經存在,但可能需要一段時間才能實現。

下一頁:6. 新字串功能